From e2991d7efc7795017217c5481d71ed7d8d6106fc Mon Sep 17 00:00:00 2001 From: Joshua Bahirvani Date: Sat, 5 Sep 2026 23:54:16 +0530 Subject: [PATCH] drift-dataset-image-upload-download --- .../versionzied_gt/D1/README.md | 144 ++++++ .../versionzied_gt/D1/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../D1/commits/C1/cdk/package.json | 15 + .../D1/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C1/commit.json | 26 ++ .../versionzied_gt/D1/commits/C1/diff.patch | 0 .../versionzied_gt/D1/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../D1/commits/C2/cdk/package.json | 15 + .../D1/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C2/commit.json | 20 + .../versionzied_gt/D1/commits/C2/diff.patch | 13 + .../versionzied_gt/D1/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../D1/commits/C3/cdk/package.json | 15 + .../D1/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C3/commit.json | 20 + .../versionzied_gt/D1/commits/C3/diff.patch | 13 + .../versionzied_gt/D1/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../D1/commits/C4/cdk/package.json | 15 + .../D1/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C4/commit.json | 20 + .../versionzied_gt/D1/commits/C4/diff.patch | 13 + .../versionzied_gt/D1/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C5/cdk/cdk.ts | 335 ++++++++++++++ .../D1/commits/C5/cdk/package.json | 15 + .../D1/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C5/commit.json | 20 + .../versionzied_gt/D1/commits/C5/diff.patch | 16 + .../versionzied_gt/D1/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../D1/commits/C6/cdk/package.json | 15 + .../D1/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C6/commit.json | 20 + .../versionzied_gt/D1/commits/C6/diff.patch | 13 + .../versionzied_gt/D1/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/D1/commits/C7/cdk/cdk.ts | 335 ++++++++++++++ .../D1/commits/C7/cdk/package.json | 15 + .../D1/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/D1/commits/C7/commit.json | 20 + .../versionzied_gt/D1/commits/C7/diff.patch | 13 + .../versionzied_gt/D1/commits/provenance.json | 107 +++++ .../versionzied_gt/D1/metadata.yaml | 120 +++++ .../versionzied_gt/D1/repository | 1 + .../versionzied_gt/D1/synth_context.json | 1 + .../versionzied_gt/D1/template_analysis.json | 209 +++++++++ .../versionzied_gt/D2/README.md | 153 +++++++ .../versionzied_gt/D2/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../D2/commits/C1/cdk/package.json | 15 + .../D2/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C1/commit.json | 26 ++ .../versionzied_gt/D2/commits/C1/diff.patch | 0 .../D2/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C10/cdk/cdk.ts | 337 ++++++++++++++ .../D2/commits/C10/cdk/package.json | 15 + .../D2/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C10/commit.json | 20 + .../versionzied_gt/D2/commits/C10/diff.patch | 13 + .../versionzied_gt/D2/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../D2/commits/C2/cdk/package.json | 15 + .../D2/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C2/commit.json | 20 + .../versionzied_gt/D2/commits/C2/diff.patch | 13 + .../versionzied_gt/D2/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../D2/commits/C3/cdk/package.json | 15 + .../D2/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C3/commit.json | 20 + .../versionzied_gt/D2/commits/C3/diff.patch | 13 + .../versionzied_gt/D2/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C4/cdk/cdk.ts | 335 ++++++++++++++ .../D2/commits/C4/cdk/package.json | 15 + .../D2/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C4/commit.json | 20 + .../versionzied_gt/D2/commits/C4/diff.patch | 16 + .../versionzied_gt/D2/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C5/cdk/cdk.ts | 335 ++++++++++++++ .../D2/commits/C5/cdk/package.json | 15 + .../D2/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C5/commit.json | 20 + .../versionzied_gt/D2/commits/C5/diff.patch | 13 + .../versionzied_gt/D2/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../D2/commits/C6/cdk/package.json | 15 + .../D2/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C6/commit.json | 20 + .../versionzied_gt/D2/commits/C6/diff.patch | 12 + .../versionzied_gt/D2/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C7/cdk/cdk.ts | 335 ++++++++++++++ .../D2/commits/C7/cdk/package.json | 15 + .../D2/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C7/commit.json | 20 + .../versionzied_gt/D2/commits/C7/diff.patch | 13 + .../versionzied_gt/D2/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C8/cdk/cdk.ts | 335 ++++++++++++++ .../D2/commits/C8/cdk/package.json | 15 + .../D2/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C8/commit.json | 20 + .../versionzied_gt/D2/commits/C8/diff.patch | 13 + .../versionzied_gt/D2/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/D2/commits/C9/cdk/cdk.ts | 337 ++++++++++++++ .../D2/commits/C9/cdk/package.json | 15 + .../D2/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/D2/commits/C9/commit.json | 20 + .../versionzied_gt/D2/commits/C9/diff.patch | 13 + .../versionzied_gt/D2/commits/provenance.json | 152 +++++++ .../versionzied_gt/D2/metadata.yaml | 148 +++++++ .../versionzied_gt/D2/repository | 1 + .../versionzied_gt/D2/synth_context.json | 1 + .../versionzied_gt/D2/template_analysis.json | 296 +++++++++++++ .../versionzied_gt/D3/README.md | 159 +++++++ .../versionzied_gt/D3/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../D3/commits/C1/cdk/package.json | 15 + .../D3/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C1/commit.json | 26 ++ .../versionzied_gt/D3/commits/C1/diff.patch | 0 .../D3/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C10/cdk/cdk.ts | 337 ++++++++++++++ .../D3/commits/C10/cdk/package.json | 15 + .../D3/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C10/commit.json | 20 + .../versionzied_gt/D3/commits/C10/diff.patch | 13 + .../D3/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C11/cdk/cdk.ts | 338 ++++++++++++++ .../D3/commits/C11/cdk/package.json | 15 + .../D3/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C11/commit.json | 20 + .../versionzied_gt/D3/commits/C11/diff.patch | 12 + .../D3/commits/C12/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C12/cdk/cdk.ts | 338 ++++++++++++++ .../D3/commits/C12/cdk/package.json | 15 + .../D3/commits/C12/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C12/commit.json | 20 + .../versionzied_gt/D3/commits/C12/diff.patch | 13 + .../versionzied_gt/D3/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../D3/commits/C2/cdk/package.json | 15 + .../D3/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C2/commit.json | 20 + .../versionzied_gt/D3/commits/C2/diff.patch | 13 + .../versionzied_gt/D3/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../D3/commits/C3/cdk/package.json | 15 + .../D3/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C3/commit.json | 20 + .../versionzied_gt/D3/commits/C3/diff.patch | 13 + .../versionzied_gt/D3/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../D3/commits/C4/cdk/package.json | 15 + .../D3/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C4/commit.json | 20 + .../versionzied_gt/D3/commits/C4/diff.patch | 13 + .../versionzied_gt/D3/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../D3/commits/C5/cdk/package.json | 15 + .../D3/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C5/commit.json | 20 + .../versionzied_gt/D3/commits/C5/diff.patch | 13 + .../versionzied_gt/D3/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../D3/commits/C6/cdk/package.json | 15 + .../D3/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C6/commit.json | 20 + .../versionzied_gt/D3/commits/C6/diff.patch | 16 + .../versionzied_gt/D3/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C7/cdk/cdk.ts | 337 ++++++++++++++ .../D3/commits/C7/cdk/package.json | 15 + .../D3/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C7/commit.json | 20 + .../versionzied_gt/D3/commits/C7/diff.patch | 13 + .../versionzied_gt/D3/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C8/cdk/cdk.ts | 337 ++++++++++++++ .../D3/commits/C8/cdk/package.json | 15 + .../D3/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C8/commit.json | 20 + .../versionzied_gt/D3/commits/C8/diff.patch | 13 + .../versionzied_gt/D3/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/D3/commits/C9/cdk/cdk.ts | 337 ++++++++++++++ .../D3/commits/C9/cdk/package.json | 15 + .../D3/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/D3/commits/C9/commit.json | 20 + .../versionzied_gt/D3/commits/C9/diff.patch | 12 + .../versionzied_gt/D3/commits/provenance.json | 182 ++++++++ .../versionzied_gt/D3/metadata.yaml | 167 +++++++ .../versionzied_gt/D3/repository | 1 + .../versionzied_gt/D3/synth_context.json | 1 + .../versionzied_gt/D3/template_analysis.json | 354 +++++++++++++++ .../versionzied_gt/D4/README.md | 165 +++++++ .../versionzied_gt/D4/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../D4/commits/C1/cdk/package.json | 15 + .../D4/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C1/commit.json | 26 ++ .../versionzied_gt/D4/commits/C1/diff.patch | 0 .../D4/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C10/cdk/cdk.ts | 338 ++++++++++++++ .../D4/commits/C10/cdk/package.json | 15 + .../D4/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C10/commit.json | 20 + .../versionzied_gt/D4/commits/C10/diff.patch | 12 + .../D4/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C11/cdk/cdk.ts | 338 ++++++++++++++ .../D4/commits/C11/cdk/package.json | 15 + .../D4/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C11/commit.json | 20 + .../versionzied_gt/D4/commits/C11/diff.patch | 13 + .../D4/commits/C12/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C12/cdk/cdk.ts | 338 ++++++++++++++ .../D4/commits/C12/cdk/package.json | 15 + .../D4/commits/C12/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C12/commit.json | 20 + .../versionzied_gt/D4/commits/C12/diff.patch | 13 + .../D4/commits/C13/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C13/cdk/cdk.ts | 338 ++++++++++++++ .../D4/commits/C13/cdk/package.json | 15 + .../D4/commits/C13/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C13/commit.json | 20 + .../versionzied_gt/D4/commits/C13/diff.patch | 11 + .../D4/commits/C14/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C14/cdk/cdk.ts | 338 ++++++++++++++ .../D4/commits/C14/cdk/package.json | 15 + .../D4/commits/C14/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C14/commit.json | 20 + .../versionzied_gt/D4/commits/C14/diff.patch | 12 + .../versionzied_gt/D4/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../D4/commits/C2/cdk/package.json | 15 + .../D4/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C2/commit.json | 20 + .../versionzied_gt/D4/commits/C2/diff.patch | 13 + .../versionzied_gt/D4/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../D4/commits/C3/cdk/package.json | 15 + .../D4/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C3/commit.json | 20 + .../versionzied_gt/D4/commits/C3/diff.patch | 13 + .../versionzied_gt/D4/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../D4/commits/C4/cdk/package.json | 15 + .../D4/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C4/commit.json | 20 + .../versionzied_gt/D4/commits/C4/diff.patch | 13 + .../versionzied_gt/D4/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../D4/commits/C5/cdk/package.json | 15 + .../D4/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C5/commit.json | 20 + .../versionzied_gt/D4/commits/C5/diff.patch | 12 + .../versionzied_gt/D4/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C6/cdk/cdk.ts | 330 ++++++++++++++ .../D4/commits/C6/cdk/package.json | 15 + .../D4/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C6/commit.json | 20 + .../versionzied_gt/D4/commits/C6/diff.patch | 13 + .../versionzied_gt/D4/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C7/cdk/cdk.ts | 335 ++++++++++++++ .../D4/commits/C7/cdk/package.json | 15 + .../D4/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C7/commit.json | 20 + .../versionzied_gt/D4/commits/C7/diff.patch | 16 + .../versionzied_gt/D4/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C8/cdk/cdk.ts | 337 ++++++++++++++ .../D4/commits/C8/cdk/package.json | 15 + .../D4/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C8/commit.json | 20 + .../versionzied_gt/D4/commits/C8/diff.patch | 13 + .../versionzied_gt/D4/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/D4/commits/C9/cdk/cdk.ts | 337 ++++++++++++++ .../D4/commits/C9/cdk/package.json | 15 + .../D4/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/D4/commits/C9/commit.json | 20 + .../versionzied_gt/D4/commits/C9/diff.patch | 13 + .../versionzied_gt/D4/commits/provenance.json | 212 +++++++++ .../versionzied_gt/D4/metadata.yaml | 186 ++++++++ .../versionzied_gt/D4/repository | 1 + .../versionzied_gt/D4/synth_context.json | 1 + .../versionzied_gt/D4/template_analysis.json | 412 ++++++++++++++++++ .../versionzied_gt/E1/README.md | 136 ++++++ .../versionzied_gt/E1/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/E1/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../E1/commits/C1/cdk/package.json | 15 + .../E1/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/E1/commits/C1/commit.json | 26 ++ .../versionzied_gt/E1/commits/C1/diff.patch | 0 .../versionzied_gt/E1/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/E1/commits/C2/cdk/cdk.ts | 333 ++++++++++++++ .../E1/commits/C2/cdk/package.json | 15 + .../E1/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/E1/commits/C2/commit.json | 20 + .../versionzied_gt/E1/commits/C2/diff.patch | 16 + .../versionzied_gt/E1/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/E1/commits/C3/cdk/cdk.ts | 338 ++++++++++++++ .../E1/commits/C3/cdk/package.json | 15 + .../E1/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/E1/commits/C3/commit.json | 20 + .../versionzied_gt/E1/commits/C3/diff.patch | 16 + .../versionzied_gt/E1/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/E1/commits/C4/cdk/cdk.ts | 338 ++++++++++++++ .../E1/commits/C4/cdk/package.json | 15 + .../E1/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/E1/commits/C4/commit.json | 20 + .../versionzied_gt/E1/commits/C4/diff.patch | 16 + .../versionzied_gt/E1/commits/provenance.json | 62 +++ .../versionzied_gt/E1/metadata.yaml | 98 +++++ .../versionzied_gt/E1/repository | 1 + .../versionzied_gt/E1/synth_context.json | 1 + .../versionzied_gt/E1/template_analysis.json | 122 ++++++ .../versionzied_gt/E2/README.md | 153 +++++++ .../versionzied_gt/E2/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../E2/commits/C1/cdk/package.json | 15 + .../E2/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C1/commit.json | 26 ++ .../versionzied_gt/E2/commits/C1/diff.patch | 0 .../E2/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C10/cdk/cdk.ts | 345 +++++++++++++++ .../E2/commits/C10/cdk/package.json | 15 + .../E2/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C10/commit.json | 20 + .../versionzied_gt/E2/commits/C10/diff.patch | 12 + .../versionzied_gt/E2/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../E2/commits/C2/cdk/package.json | 15 + .../E2/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C2/commit.json | 20 + .../versionzied_gt/E2/commits/C2/diff.patch | 13 + .../versionzied_gt/E2/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../E2/commits/C3/cdk/package.json | 15 + .../E2/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C3/commit.json | 20 + .../versionzied_gt/E2/commits/C3/diff.patch | 13 + .../versionzied_gt/E2/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C4/cdk/cdk.ts | 338 ++++++++++++++ .../E2/commits/C4/cdk/package.json | 15 + .../E2/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C4/commit.json | 20 + .../versionzied_gt/E2/commits/C4/diff.patch | 19 + .../versionzied_gt/E2/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C5/cdk/cdk.ts | 343 +++++++++++++++ .../E2/commits/C5/cdk/package.json | 15 + .../E2/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C5/commit.json | 20 + .../versionzied_gt/E2/commits/C5/diff.patch | 16 + .../versionzied_gt/E2/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C6/cdk/cdk.ts | 345 +++++++++++++++ .../E2/commits/C6/cdk/package.json | 15 + .../E2/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C6/commit.json | 20 + .../versionzied_gt/E2/commits/C6/diff.patch | 13 + .../versionzied_gt/E2/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C7/cdk/cdk.ts | 345 +++++++++++++++ .../E2/commits/C7/cdk/package.json | 15 + .../E2/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C7/commit.json | 20 + .../versionzied_gt/E2/commits/C7/diff.patch | 13 + .../versionzied_gt/E2/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C8/cdk/cdk.ts | 345 +++++++++++++++ .../E2/commits/C8/cdk/package.json | 15 + .../E2/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C8/commit.json | 20 + .../versionzied_gt/E2/commits/C8/diff.patch | 13 + .../versionzied_gt/E2/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/E2/commits/C9/cdk/cdk.ts | 345 +++++++++++++++ .../E2/commits/C9/cdk/package.json | 15 + .../E2/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/E2/commits/C9/commit.json | 20 + .../versionzied_gt/E2/commits/C9/diff.patch | 11 + .../versionzied_gt/E2/commits/provenance.json | 152 +++++++ .../versionzied_gt/E2/metadata.yaml | 150 +++++++ .../versionzied_gt/E2/repository | 1 + .../versionzied_gt/E2/synth_context.json | 1 + .../versionzied_gt/E2/template_analysis.json | 296 +++++++++++++ .../versionzied_gt/E3/README.md | 145 ++++++ .../versionzied_gt/E3/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../E3/commits/C1/cdk/package.json | 15 + .../E3/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C1/commit.json | 26 ++ .../versionzied_gt/E3/commits/C1/diff.patch | 0 .../versionzied_gt/E3/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../E3/commits/C2/cdk/package.json | 15 + .../E3/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C2/commit.json | 20 + .../versionzied_gt/E3/commits/C2/diff.patch | 13 + .../versionzied_gt/E3/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../E3/commits/C3/cdk/package.json | 15 + .../E3/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C3/commit.json | 20 + .../versionzied_gt/E3/commits/C3/diff.patch | 13 + .../versionzied_gt/E3/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C4/cdk/cdk.ts | 335 ++++++++++++++ .../E3/commits/C4/cdk/package.json | 15 + .../E3/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C4/commit.json | 20 + .../versionzied_gt/E3/commits/C4/diff.patch | 16 + .../versionzied_gt/E3/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C5/cdk/cdk.ts | 335 ++++++++++++++ .../E3/commits/C5/cdk/package.json | 15 + .../E3/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C5/commit.json | 20 + .../versionzied_gt/E3/commits/C5/diff.patch | 13 + .../versionzied_gt/E3/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../E3/commits/C6/cdk/package.json | 15 + .../E3/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C6/commit.json | 20 + .../versionzied_gt/E3/commits/C6/diff.patch | 13 + .../versionzied_gt/E3/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/E3/commits/C7/cdk/cdk.ts | 335 ++++++++++++++ .../E3/commits/C7/cdk/package.json | 15 + .../E3/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/E3/commits/C7/commit.json | 20 + .../versionzied_gt/E3/commits/C7/diff.patch | 13 + .../versionzied_gt/E3/commits/provenance.json | 107 +++++ .../versionzied_gt/E3/metadata.yaml | 123 ++++++ .../versionzied_gt/E3/repository | 1 + .../versionzied_gt/E3/synth_context.json | 1 + .../versionzied_gt/E3/template_analysis.json | 209 +++++++++ .../versionzied_gt/E4/README.md | 162 +++++++ .../versionzied_gt/E4/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../E4/commits/C1/cdk/package.json | 15 + .../E4/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C1/commit.json | 26 ++ .../versionzied_gt/E4/commits/C1/diff.patch | 0 .../E4/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C10/cdk/cdk.ts | 343 +++++++++++++++ .../E4/commits/C10/cdk/package.json | 15 + .../E4/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C10/commit.json | 20 + .../versionzied_gt/E4/commits/C10/diff.patch | 13 + .../E4/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C11/cdk/cdk.ts | 344 +++++++++++++++ .../E4/commits/C11/cdk/package.json | 15 + .../E4/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C11/commit.json | 20 + .../versionzied_gt/E4/commits/C11/diff.patch | 12 + .../E4/commits/C12/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C12/cdk/cdk.ts | 344 +++++++++++++++ .../E4/commits/C12/cdk/package.json | 15 + .../E4/commits/C12/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C12/commit.json | 20 + .../versionzied_gt/E4/commits/C12/diff.patch | 13 + .../E4/commits/C13/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C13/cdk/cdk.ts | 344 +++++++++++++++ .../E4/commits/C13/cdk/package.json | 15 + .../E4/commits/C13/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C13/commit.json | 20 + .../versionzied_gt/E4/commits/C13/diff.patch | 11 + .../versionzied_gt/E4/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../E4/commits/C2/cdk/package.json | 15 + .../E4/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C2/commit.json | 20 + .../versionzied_gt/E4/commits/C2/diff.patch | 13 + .../versionzied_gt/E4/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../E4/commits/C3/cdk/package.json | 15 + .../E4/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C3/commit.json | 20 + .../versionzied_gt/E4/commits/C3/diff.patch | 13 + .../versionzied_gt/E4/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../E4/commits/C4/cdk/package.json | 15 + .../E4/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C4/commit.json | 20 + .../versionzied_gt/E4/commits/C4/diff.patch | 13 + .../versionzied_gt/E4/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../E4/commits/C5/cdk/package.json | 15 + .../E4/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C5/commit.json | 20 + .../versionzied_gt/E4/commits/C5/diff.patch | 12 + .../versionzied_gt/E4/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C6/cdk/cdk.ts | 336 ++++++++++++++ .../E4/commits/C6/cdk/package.json | 15 + .../E4/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C6/commit.json | 20 + .../versionzied_gt/E4/commits/C6/diff.patch | 17 + .../versionzied_gt/E4/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C7/cdk/cdk.ts | 341 +++++++++++++++ .../E4/commits/C7/cdk/package.json | 15 + .../E4/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C7/commit.json | 20 + .../versionzied_gt/E4/commits/C7/diff.patch | 16 + .../versionzied_gt/E4/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C8/cdk/cdk.ts | 343 +++++++++++++++ .../E4/commits/C8/cdk/package.json | 15 + .../E4/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C8/commit.json | 20 + .../versionzied_gt/E4/commits/C8/diff.patch | 13 + .../versionzied_gt/E4/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/E4/commits/C9/cdk/cdk.ts | 343 +++++++++++++++ .../E4/commits/C9/cdk/package.json | 15 + .../E4/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/E4/commits/C9/commit.json | 20 + .../versionzied_gt/E4/commits/C9/diff.patch | 13 + .../versionzied_gt/E4/commits/provenance.json | 197 +++++++++ .../versionzied_gt/E4/metadata.yaml | 177 ++++++++ .../versionzied_gt/E4/repository | 1 + .../versionzied_gt/E4/synth_context.json | 1 + .../versionzied_gt/E4/template_analysis.json | 383 ++++++++++++++++ .../versionzied_gt/E5/README.md | 141 ++++++ .../versionzied_gt/E5/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/E5/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../E5/commits/C1/cdk/package.json | 15 + .../E5/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/E5/commits/C1/commit.json | 26 ++ .../versionzied_gt/E5/commits/C1/diff.patch | 0 .../versionzied_gt/E5/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/E5/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../E5/commits/C2/cdk/package.json | 15 + .../E5/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/E5/commits/C2/commit.json | 20 + .../versionzied_gt/E5/commits/C2/diff.patch | 13 + .../versionzied_gt/E5/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/E5/commits/C3/cdk/cdk.ts | 335 ++++++++++++++ .../E5/commits/C3/cdk/package.json | 15 + .../E5/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/E5/commits/C3/commit.json | 20 + .../versionzied_gt/E5/commits/C3/diff.patch | 16 + .../versionzied_gt/E5/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/E5/commits/C4/cdk/cdk.ts | 342 +++++++++++++++ .../E5/commits/C4/cdk/package.json | 15 + .../E5/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/E5/commits/C4/commit.json | 20 + .../versionzied_gt/E5/commits/C4/diff.patch | 18 + .../versionzied_gt/E5/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/E5/commits/C5/cdk/cdk.ts | 342 +++++++++++++++ .../E5/commits/C5/cdk/package.json | 15 + .../E5/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/E5/commits/C5/commit.json | 20 + .../versionzied_gt/E5/commits/C5/diff.patch | 13 + .../versionzied_gt/E5/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/E5/commits/C6/cdk/cdk.ts | 342 +++++++++++++++ .../E5/commits/C6/cdk/package.json | 15 + .../E5/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/E5/commits/C6/commit.json | 20 + .../versionzied_gt/E5/commits/C6/diff.patch | 13 + .../versionzied_gt/E5/commits/provenance.json | 92 ++++ .../versionzied_gt/E5/metadata.yaml | 112 +++++ .../versionzied_gt/E5/repository | 1 + .../versionzied_gt/E5/synth_context.json | 1 + .../versionzied_gt/E5/template_analysis.json | 180 ++++++++ .../versionzied_gt/E6/README.md | 166 +++++++ .../versionzied_gt/E6/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C1/cdk/package.json | 15 + .../E6/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C1/commit.json | 26 ++ .../versionzied_gt/E6/commits/C1/diff.patch | 0 .../E6/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C10/cdk/cdk.ts | 338 ++++++++++++++ .../E6/commits/C10/cdk/package.json | 15 + .../E6/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C10/commit.json | 20 + .../versionzied_gt/E6/commits/C10/diff.patch | 12 + .../E6/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C11/cdk/cdk.ts | 338 ++++++++++++++ .../E6/commits/C11/cdk/package.json | 15 + .../E6/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C11/commit.json | 20 + .../versionzied_gt/E6/commits/C11/diff.patch | 13 + .../E6/commits/C12/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C12/cdk/cdk.ts | 338 ++++++++++++++ .../E6/commits/C12/cdk/package.json | 15 + .../E6/commits/C12/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C12/commit.json | 20 + .../versionzied_gt/E6/commits/C12/diff.patch | 13 + .../E6/commits/C13/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C13/cdk/cdk.ts | 338 ++++++++++++++ .../E6/commits/C13/cdk/package.json | 15 + .../E6/commits/C13/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C13/commit.json | 20 + .../versionzied_gt/E6/commits/C13/diff.patch | 11 + .../E6/commits/C14/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C14/cdk/cdk.ts | 338 ++++++++++++++ .../E6/commits/C14/cdk/package.json | 15 + .../E6/commits/C14/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C14/commit.json | 20 + .../versionzied_gt/E6/commits/C14/diff.patch | 12 + .../versionzied_gt/E6/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C2/cdk/package.json | 15 + .../E6/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C2/commit.json | 20 + .../versionzied_gt/E6/commits/C2/diff.patch | 13 + .../versionzied_gt/E6/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C3/cdk/package.json | 15 + .../E6/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C3/commit.json | 20 + .../versionzied_gt/E6/commits/C3/diff.patch | 13 + .../versionzied_gt/E6/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C4/cdk/package.json | 15 + .../E6/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C4/commit.json | 20 + .../versionzied_gt/E6/commits/C4/diff.patch | 13 + .../versionzied_gt/E6/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C5/cdk/package.json | 15 + .../E6/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C5/commit.json | 20 + .../versionzied_gt/E6/commits/C5/diff.patch | 12 + .../versionzied_gt/E6/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C6/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C6/cdk/package.json | 15 + .../E6/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C6/commit.json | 20 + .../versionzied_gt/E6/commits/C6/diff.patch | 13 + .../versionzied_gt/E6/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C7/cdk/cdk.ts | 330 ++++++++++++++ .../E6/commits/C7/cdk/package.json | 15 + .../E6/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C7/commit.json | 20 + .../versionzied_gt/E6/commits/C7/diff.patch | 13 + .../versionzied_gt/E6/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C8/cdk/cdk.ts | 335 ++++++++++++++ .../E6/commits/C8/cdk/package.json | 15 + .../E6/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C8/commit.json | 20 + .../versionzied_gt/E6/commits/C8/diff.patch | 16 + .../versionzied_gt/E6/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/E6/commits/C9/cdk/cdk.ts | 337 ++++++++++++++ .../E6/commits/C9/cdk/package.json | 15 + .../E6/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/E6/commits/C9/commit.json | 20 + .../versionzied_gt/E6/commits/C9/diff.patch | 13 + .../versionzied_gt/E6/commits/provenance.json | 212 +++++++++ .../versionzied_gt/E6/metadata.yaml | 188 ++++++++ .../versionzied_gt/E6/repository | 1 + .../versionzied_gt/E6/synth_context.json | 1 + .../versionzied_gt/E6/template_analysis.json | 412 ++++++++++++++++++ .../versionzied_gt/G1/README.md | 147 +++++++ .../versionzied_gt/G1/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../G1/commits/C1/cdk/package.json | 15 + .../G1/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C1/commit.json | 26 ++ .../versionzied_gt/G1/commits/C1/diff.patch | 0 .../versionzied_gt/G1/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../G1/commits/C2/cdk/package.json | 15 + .../G1/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C2/commit.json | 20 + .../versionzied_gt/G1/commits/C2/diff.patch | 13 + .../versionzied_gt/G1/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../G1/commits/C3/cdk/package.json | 15 + .../G1/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C3/commit.json | 20 + .../versionzied_gt/G1/commits/C3/diff.patch | 13 + .../versionzied_gt/G1/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../G1/commits/C4/cdk/package.json | 15 + .../G1/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C4/commit.json | 20 + .../versionzied_gt/G1/commits/C4/diff.patch | 13 + .../versionzied_gt/G1/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C5/cdk/cdk.ts | 335 ++++++++++++++ .../G1/commits/C5/cdk/package.json | 15 + .../G1/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C5/commit.json | 20 + .../versionzied_gt/G1/commits/C5/diff.patch | 16 + .../versionzied_gt/G1/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../G1/commits/C6/cdk/package.json | 15 + .../G1/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C6/commit.json | 20 + .../versionzied_gt/G1/commits/C6/diff.patch | 12 + .../versionzied_gt/G1/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C7/cdk/cdk.ts | 337 ++++++++++++++ .../G1/commits/C7/cdk/package.json | 15 + .../G1/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C7/commit.json | 20 + .../versionzied_gt/G1/commits/C7/diff.patch | 13 + .../versionzied_gt/G1/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/G1/commits/C8/cdk/cdk.ts | 337 ++++++++++++++ .../G1/commits/C8/cdk/package.json | 15 + .../G1/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/G1/commits/C8/commit.json | 20 + .../versionzied_gt/G1/commits/C8/diff.patch | 13 + .../versionzied_gt/G1/commits/provenance.json | 122 ++++++ .../versionzied_gt/G1/metadata.yaml | 130 ++++++ .../versionzied_gt/G1/repository | 1 + .../versionzied_gt/G1/synth_context.json | 1 + .../versionzied_gt/G1/template_analysis.json | 254 +++++++++++ .../versionzied_gt/G2/README.md | 138 ++++++ .../versionzied_gt/G2/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/G2/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../G2/commits/C1/cdk/package.json | 15 + .../G2/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/G2/commits/C1/commit.json | 26 ++ .../versionzied_gt/G2/commits/C1/diff.patch | 0 .../versionzied_gt/G2/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/G2/commits/C2/cdk/cdk.ts | 335 ++++++++++++++ .../G2/commits/C2/cdk/package.json | 15 + .../G2/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/G2/commits/C2/commit.json | 20 + .../versionzied_gt/G2/commits/C2/diff.patch | 16 + .../versionzied_gt/G2/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/G2/commits/C3/cdk/cdk.ts | 340 +++++++++++++++ .../G2/commits/C3/cdk/package.json | 15 + .../G2/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/G2/commits/C3/commit.json | 20 + .../versionzied_gt/G2/commits/C3/diff.patch | 16 + .../versionzied_gt/G2/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/G2/commits/C4/cdk/cdk.ts | 340 +++++++++++++++ .../G2/commits/C4/cdk/package.json | 15 + .../G2/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/G2/commits/C4/commit.json | 20 + .../versionzied_gt/G2/commits/C4/diff.patch | 15 + .../versionzied_gt/G2/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/G2/commits/C5/cdk/cdk.ts | 340 +++++++++++++++ .../G2/commits/C5/cdk/package.json | 15 + .../G2/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/G2/commits/C5/commit.json | 20 + .../versionzied_gt/G2/commits/C5/diff.patch | 13 + .../versionzied_gt/G2/commits/provenance.json | 77 ++++ .../versionzied_gt/G2/metadata.yaml | 102 +++++ .../versionzied_gt/G2/repository | 1 + .../versionzied_gt/G2/synth_context.json | 1 + .../versionzied_gt/G2/template_analysis.json | 159 +++++++ .../versionzied_gt/G3/README.md | 159 +++++++ .../versionzied_gt/G3/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../G3/commits/C1/cdk/package.json | 15 + .../G3/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C1/commit.json | 26 ++ .../versionzied_gt/G3/commits/C1/diff.patch | 0 .../G3/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C10/cdk/cdk.ts | 342 +++++++++++++++ .../G3/commits/C10/cdk/package.json | 15 + .../G3/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C10/commit.json | 20 + .../versionzied_gt/G3/commits/C10/diff.patch | 13 + .../G3/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C11/cdk/cdk.ts | 343 +++++++++++++++ .../G3/commits/C11/cdk/package.json | 15 + .../G3/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C11/commit.json | 20 + .../versionzied_gt/G3/commits/C11/diff.patch | 12 + .../G3/commits/C12/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C12/cdk/cdk.ts | 343 +++++++++++++++ .../G3/commits/C12/cdk/package.json | 15 + .../G3/commits/C12/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C12/commit.json | 20 + .../versionzied_gt/G3/commits/C12/diff.patch | 13 + .../versionzied_gt/G3/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../G3/commits/C2/cdk/package.json | 15 + .../G3/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C2/commit.json | 20 + .../versionzied_gt/G3/commits/C2/diff.patch | 13 + .../versionzied_gt/G3/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../G3/commits/C3/cdk/package.json | 15 + .../G3/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C3/commit.json | 20 + .../versionzied_gt/G3/commits/C3/diff.patch | 13 + .../versionzied_gt/G3/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../G3/commits/C4/cdk/package.json | 15 + .../G3/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C4/commit.json | 20 + .../versionzied_gt/G3/commits/C4/diff.patch | 13 + .../versionzied_gt/G3/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../G3/commits/C5/cdk/package.json | 15 + .../G3/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C5/commit.json | 20 + .../versionzied_gt/G3/commits/C5/diff.patch | 12 + .../versionzied_gt/G3/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../G3/commits/C6/cdk/package.json | 15 + .../G3/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C6/commit.json | 20 + .../versionzied_gt/G3/commits/C6/diff.patch | 16 + .../versionzied_gt/G3/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C7/cdk/cdk.ts | 340 +++++++++++++++ .../G3/commits/C7/cdk/package.json | 15 + .../G3/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C7/commit.json | 20 + .../versionzied_gt/G3/commits/C7/diff.patch | 16 + .../versionzied_gt/G3/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C8/cdk/cdk.ts | 340 +++++++++++++++ .../G3/commits/C8/cdk/package.json | 15 + .../G3/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C8/commit.json | 20 + .../versionzied_gt/G3/commits/C8/diff.patch | 13 + .../versionzied_gt/G3/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/G3/commits/C9/cdk/cdk.ts | 340 +++++++++++++++ .../G3/commits/C9/cdk/package.json | 15 + .../G3/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/G3/commits/C9/commit.json | 20 + .../versionzied_gt/G3/commits/C9/diff.patch | 12 + .../versionzied_gt/G3/commits/provenance.json | 182 ++++++++ .../versionzied_gt/G3/metadata.yaml | 167 +++++++ .../versionzied_gt/G3/repository | 1 + .../versionzied_gt/G3/synth_context.json | 1 + .../versionzied_gt/G3/template_analysis.json | 368 ++++++++++++++++ .../versionzied_gt/G4/README.md | 135 ++++++ .../versionzied_gt/G4/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/G4/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../G4/commits/C1/cdk/package.json | 15 + .../G4/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/G4/commits/C1/commit.json | 26 ++ .../versionzied_gt/G4/commits/C1/diff.patch | 0 .../versionzied_gt/G4/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/G4/commits/C2/cdk/cdk.ts | 332 ++++++++++++++ .../G4/commits/C2/cdk/package.json | 15 + .../G4/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/G4/commits/C2/commit.json | 20 + .../versionzied_gt/G4/commits/C2/diff.patch | 13 + .../versionzied_gt/G4/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/G4/commits/C3/cdk/cdk.ts | 332 ++++++++++++++ .../G4/commits/C3/cdk/package.json | 15 + .../G4/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/G4/commits/C3/commit.json | 20 + .../versionzied_gt/G4/commits/C3/diff.patch | 13 + .../versionzied_gt/G4/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/G4/commits/C4/cdk/cdk.ts | 337 ++++++++++++++ .../G4/commits/C4/cdk/package.json | 15 + .../G4/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/G4/commits/C4/commit.json | 20 + .../versionzied_gt/G4/commits/C4/diff.patch | 16 + .../versionzied_gt/G4/commits/provenance.json | 62 +++ .../versionzied_gt/G4/metadata.yaml | 92 ++++ .../versionzied_gt/G4/repository | 1 + .../versionzied_gt/G4/synth_context.json | 1 + .../versionzied_gt/G4/template_analysis.json | 125 ++++++ .../versionzied_gt/PULL_REQUEST.md | 148 +++++++ .../versionzied_gt/R1/README.md | 150 +++++++ .../versionzied_gt/R1/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../R1/commits/C1/cdk/package.json | 15 + .../R1/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C1/commit.json | 26 ++ .../versionzied_gt/R1/commits/C1/diff.patch | 0 .../versionzied_gt/R1/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../R1/commits/C2/cdk/package.json | 15 + .../R1/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C2/commit.json | 20 + .../versionzied_gt/R1/commits/C2/diff.patch | 13 + .../versionzied_gt/R1/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../R1/commits/C3/cdk/package.json | 15 + .../R1/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C3/commit.json | 20 + .../versionzied_gt/R1/commits/C3/diff.patch | 13 + .../versionzied_gt/R1/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C4/cdk/cdk.ts | 331 ++++++++++++++ .../R1/commits/C4/cdk/package.json | 15 + .../R1/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C4/commit.json | 20 + .../versionzied_gt/R1/commits/C4/diff.patch | 12 + .../versionzied_gt/R1/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C5/cdk/cdk.ts | 336 ++++++++++++++ .../R1/commits/C5/cdk/package.json | 15 + .../R1/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C5/commit.json | 20 + .../versionzied_gt/R1/commits/C5/diff.patch | 16 + .../versionzied_gt/R1/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C6/cdk/cdk.ts | 336 ++++++++++++++ .../R1/commits/C6/cdk/package.json | 15 + .../R1/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C6/commit.json | 20 + .../versionzied_gt/R1/commits/C6/diff.patch | 13 + .../versionzied_gt/R1/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C7/cdk/cdk.ts | 338 ++++++++++++++ .../R1/commits/C7/cdk/package.json | 15 + .../R1/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C7/commit.json | 20 + .../versionzied_gt/R1/commits/C7/diff.patch | 13 + .../versionzied_gt/R1/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C8/cdk/cdk.ts | 338 ++++++++++++++ .../R1/commits/C8/cdk/package.json | 15 + .../R1/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C8/commit.json | 20 + .../versionzied_gt/R1/commits/C8/diff.patch | 13 + .../versionzied_gt/R1/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/R1/commits/C9/cdk/cdk.ts | 338 ++++++++++++++ .../R1/commits/C9/cdk/package.json | 15 + .../R1/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/R1/commits/C9/commit.json | 20 + .../versionzied_gt/R1/commits/C9/diff.patch | 11 + .../versionzied_gt/R1/commits/provenance.json | 137 ++++++ .../versionzied_gt/R1/metadata.yaml | 141 ++++++ .../versionzied_gt/R1/repository | 1 + .../versionzied_gt/R1/synth_context.json | 1 + .../versionzied_gt/R1/template_analysis.json | 267 ++++++++++++ .../versionzied_gt/R2/README.md | 142 ++++++ .../versionzied_gt/R2/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/R2/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../R2/commits/C1/cdk/package.json | 15 + .../R2/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/R2/commits/C1/commit.json | 26 ++ .../versionzied_gt/R2/commits/C1/diff.patch | 0 .../versionzied_gt/R2/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/R2/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../R2/commits/C2/cdk/package.json | 15 + .../R2/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/R2/commits/C2/commit.json | 20 + .../versionzied_gt/R2/commits/C2/diff.patch | 13 + .../versionzied_gt/R2/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/R2/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../R2/commits/C3/cdk/package.json | 15 + .../R2/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/R2/commits/C3/commit.json | 20 + .../versionzied_gt/R2/commits/C3/diff.patch | 22 + .../versionzied_gt/R2/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/R2/commits/C4/cdk/cdk.ts | 335 ++++++++++++++ .../R2/commits/C4/cdk/package.json | 15 + .../R2/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/R2/commits/C4/commit.json | 20 + .../versionzied_gt/R2/commits/C4/diff.patch | 16 + .../versionzied_gt/R2/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/R2/commits/C5/cdk/cdk.ts | 335 ++++++++++++++ .../R2/commits/C5/cdk/package.json | 15 + .../R2/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/R2/commits/C5/commit.json | 20 + .../versionzied_gt/R2/commits/C5/diff.patch | 13 + .../versionzied_gt/R2/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/R2/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../R2/commits/C6/cdk/package.json | 15 + .../R2/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/R2/commits/C6/commit.json | 20 + .../versionzied_gt/R2/commits/C6/diff.patch | 13 + .../versionzied_gt/R2/commits/provenance.json | 92 ++++ .../versionzied_gt/R2/metadata.yaml | 114 +++++ .../versionzied_gt/R2/repository | 1 + .../versionzied_gt/R2/synth_context.json | 1 + .../versionzied_gt/R2/template_analysis.json | 178 ++++++++ .../versionzied_gt/R3/README.md | 156 +++++++ .../versionzied_gt/R3/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../R3/commits/C1/cdk/package.json | 15 + .../R3/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C1/commit.json | 26 ++ .../versionzied_gt/R3/commits/C1/diff.patch | 0 .../R3/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C10/cdk/cdk.ts | 338 ++++++++++++++ .../R3/commits/C10/cdk/package.json | 15 + .../R3/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C10/commit.json | 20 + .../versionzied_gt/R3/commits/C10/diff.patch | 12 + .../R3/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C11/cdk/cdk.ts | 338 ++++++++++++++ .../R3/commits/C11/cdk/package.json | 15 + .../R3/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C11/commit.json | 20 + .../versionzied_gt/R3/commits/C11/diff.patch | 13 + .../versionzied_gt/R3/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../R3/commits/C2/cdk/package.json | 15 + .../R3/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C2/commit.json | 20 + .../versionzied_gt/R3/commits/C2/diff.patch | 13 + .../versionzied_gt/R3/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../R3/commits/C3/cdk/package.json | 15 + .../R3/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C3/commit.json | 20 + .../versionzied_gt/R3/commits/C3/diff.patch | 13 + .../versionzied_gt/R3/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../R3/commits/C4/cdk/package.json | 15 + .../R3/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C4/commit.json | 20 + .../versionzied_gt/R3/commits/C4/diff.patch | 13 + .../versionzied_gt/R3/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../R3/commits/C5/cdk/package.json | 15 + .../R3/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C5/commit.json | 20 + .../versionzied_gt/R3/commits/C5/diff.patch | 13 + .../versionzied_gt/R3/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C6/cdk/cdk.ts | 335 ++++++++++++++ .../R3/commits/C6/cdk/package.json | 15 + .../R3/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C6/commit.json | 20 + .../versionzied_gt/R3/commits/C6/diff.patch | 16 + .../versionzied_gt/R3/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C7/cdk/cdk.ts | 335 ++++++++++++++ .../R3/commits/C7/cdk/package.json | 15 + .../R3/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C7/commit.json | 20 + .../versionzied_gt/R3/commits/C7/diff.patch | 13 + .../versionzied_gt/R3/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C8/cdk/cdk.ts | 335 ++++++++++++++ .../R3/commits/C8/cdk/package.json | 15 + .../R3/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C8/commit.json | 20 + .../versionzied_gt/R3/commits/C8/diff.patch | 13 + .../versionzied_gt/R3/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/R3/commits/C9/cdk/cdk.ts | 337 ++++++++++++++ .../R3/commits/C9/cdk/package.json | 15 + .../R3/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/R3/commits/C9/commit.json | 20 + .../versionzied_gt/R3/commits/C9/diff.patch | 13 + .../versionzied_gt/R3/commits/provenance.json | 167 +++++++ .../versionzied_gt/R3/metadata.yaml | 159 +++++++ .../versionzied_gt/R3/repository | 1 + .../versionzied_gt/R3/synth_context.json | 1 + .../versionzied_gt/R3/template_analysis.json | 325 ++++++++++++++ .../versionzied_gt/README.md | 127 ++++++ .../versionzied_gt/T1/README.md | 147 +++++++ .../versionzied_gt/T1/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../T1/commits/C1/cdk/package.json | 15 + .../T1/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C1/commit.json | 26 ++ .../versionzied_gt/T1/commits/C1/diff.patch | 0 .../versionzied_gt/T1/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../T1/commits/C2/cdk/package.json | 15 + .../T1/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C2/commit.json | 20 + .../versionzied_gt/T1/commits/C2/diff.patch | 13 + .../versionzied_gt/T1/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C3/cdk/cdk.ts | 338 ++++++++++++++ .../T1/commits/C3/cdk/package.json | 15 + .../T1/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C3/commit.json | 20 + .../versionzied_gt/T1/commits/C3/diff.patch | 19 + .../versionzied_gt/T1/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C4/cdk/cdk.ts | 343 +++++++++++++++ .../T1/commits/C4/cdk/package.json | 15 + .../T1/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C4/commit.json | 20 + .../versionzied_gt/T1/commits/C4/diff.patch | 16 + .../versionzied_gt/T1/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C5/cdk/cdk.ts | 343 +++++++++++++++ .../T1/commits/C5/cdk/package.json | 15 + .../T1/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C5/commit.json | 20 + .../versionzied_gt/T1/commits/C5/diff.patch | 13 + .../versionzied_gt/T1/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C6/cdk/cdk.ts | 343 +++++++++++++++ .../T1/commits/C6/cdk/package.json | 15 + .../T1/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C6/commit.json | 20 + .../versionzied_gt/T1/commits/C6/diff.patch | 14 + .../versionzied_gt/T1/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C7/cdk/cdk.ts | 345 +++++++++++++++ .../T1/commits/C7/cdk/package.json | 15 + .../T1/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C7/commit.json | 20 + .../versionzied_gt/T1/commits/C7/diff.patch | 13 + .../versionzied_gt/T1/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/T1/commits/C8/cdk/cdk.ts | 345 +++++++++++++++ .../T1/commits/C8/cdk/package.json | 15 + .../T1/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/T1/commits/C8/commit.json | 20 + .../versionzied_gt/T1/commits/C8/diff.patch | 13 + .../versionzied_gt/T1/commits/provenance.json | 122 ++++++ .../versionzied_gt/T1/metadata.yaml | 132 ++++++ .../versionzied_gt/T1/repository | 1 + .../versionzied_gt/T1/synth_context.json | 1 + .../versionzied_gt/T1/template_analysis.json | 238 ++++++++++ .../versionzied_gt/T2/README.md | 149 +++++++ .../versionzied_gt/T2/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../T2/commits/C1/cdk/package.json | 15 + .../T2/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C1/commit.json | 26 ++ .../versionzied_gt/T2/commits/C1/diff.patch | 0 .../versionzied_gt/T2/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../T2/commits/C2/cdk/package.json | 15 + .../T2/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C2/commit.json | 20 + .../versionzied_gt/T2/commits/C2/diff.patch | 13 + .../versionzied_gt/T2/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../T2/commits/C3/cdk/package.json | 15 + .../T2/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C3/commit.json | 20 + .../versionzied_gt/T2/commits/C3/diff.patch | 13 + .../versionzied_gt/T2/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../T2/commits/C4/cdk/package.json | 15 + .../T2/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C4/commit.json | 20 + .../versionzied_gt/T2/commits/C4/diff.patch | 12 + .../versionzied_gt/T2/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C5/cdk/cdk.ts | 335 ++++++++++++++ .../T2/commits/C5/cdk/package.json | 15 + .../T2/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C5/commit.json | 20 + .../versionzied_gt/T2/commits/C5/diff.patch | 16 + .../versionzied_gt/T2/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C6/cdk/cdk.ts | 340 +++++++++++++++ .../T2/commits/C6/cdk/package.json | 15 + .../T2/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C6/commit.json | 20 + .../versionzied_gt/T2/commits/C6/diff.patch | 16 + .../versionzied_gt/T2/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C7/cdk/cdk.ts | 342 +++++++++++++++ .../T2/commits/C7/cdk/package.json | 15 + .../T2/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C7/commit.json | 20 + .../versionzied_gt/T2/commits/C7/diff.patch | 13 + .../versionzied_gt/T2/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C8/cdk/cdk.ts | 342 +++++++++++++++ .../T2/commits/C8/cdk/package.json | 15 + .../T2/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C8/commit.json | 20 + .../versionzied_gt/T2/commits/C8/diff.patch | 13 + .../versionzied_gt/T2/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/T2/commits/C9/cdk/cdk.ts | 342 +++++++++++++++ .../T2/commits/C9/cdk/package.json | 15 + .../T2/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/T2/commits/C9/commit.json | 20 + .../versionzied_gt/T2/commits/C9/diff.patch | 13 + .../versionzied_gt/T2/commits/provenance.json | 137 ++++++ .../versionzied_gt/T2/metadata.yaml | 140 ++++++ .../versionzied_gt/T2/repository | 1 + .../versionzied_gt/T2/synth_context.json | 1 + .../versionzied_gt/T2/template_analysis.json | 272 ++++++++++++ .../versionzied_gt/T3/README.md | 156 +++++++ .../versionzied_gt/T3/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../T3/commits/C1/cdk/package.json | 15 + .../T3/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C1/commit.json | 26 ++ .../versionzied_gt/T3/commits/C1/diff.patch | 0 .../T3/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C10/cdk/cdk.ts | 348 +++++++++++++++ .../T3/commits/C10/cdk/package.json | 15 + .../T3/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C10/commit.json | 20 + .../versionzied_gt/T3/commits/C10/diff.patch | 13 + .../T3/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C11/cdk/cdk.ts | 348 +++++++++++++++ .../T3/commits/C11/cdk/package.json | 15 + .../T3/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C11/commit.json | 20 + .../versionzied_gt/T3/commits/C11/diff.patch | 13 + .../versionzied_gt/T3/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../T3/commits/C2/cdk/package.json | 15 + .../T3/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C2/commit.json | 20 + .../versionzied_gt/T3/commits/C2/diff.patch | 13 + .../versionzied_gt/T3/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../T3/commits/C3/cdk/package.json | 15 + .../T3/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C3/commit.json | 20 + .../versionzied_gt/T3/commits/C3/diff.patch | 13 + .../versionzied_gt/T3/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C4/cdk/cdk.ts | 341 +++++++++++++++ .../T3/commits/C4/cdk/package.json | 15 + .../T3/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C4/commit.json | 20 + .../versionzied_gt/T3/commits/C4/diff.patch | 22 + .../versionzied_gt/T3/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C5/cdk/cdk.ts | 346 +++++++++++++++ .../T3/commits/C5/cdk/package.json | 15 + .../T3/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C5/commit.json | 20 + .../versionzied_gt/T3/commits/C5/diff.patch | 16 + .../versionzied_gt/T3/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C6/cdk/cdk.ts | 346 +++++++++++++++ .../T3/commits/C6/cdk/package.json | 15 + .../T3/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C6/commit.json | 20 + .../versionzied_gt/T3/commits/C6/diff.patch | 13 + .../versionzied_gt/T3/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C7/cdk/cdk.ts | 346 +++++++++++++++ .../T3/commits/C7/cdk/package.json | 15 + .../T3/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C7/commit.json | 20 + .../versionzied_gt/T3/commits/C7/diff.patch | 12 + .../versionzied_gt/T3/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C8/cdk/cdk.ts | 346 +++++++++++++++ .../T3/commits/C8/cdk/package.json | 15 + .../T3/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C8/commit.json | 20 + .../versionzied_gt/T3/commits/C8/diff.patch | 13 + .../versionzied_gt/T3/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/T3/commits/C9/cdk/cdk.ts | 346 +++++++++++++++ .../T3/commits/C9/cdk/package.json | 15 + .../T3/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/T3/commits/C9/commit.json | 20 + .../versionzied_gt/T3/commits/C9/diff.patch | 12 + .../versionzied_gt/T3/commits/provenance.json | 167 +++++++ .../versionzied_gt/T3/metadata.yaml | 158 +++++++ .../versionzied_gt/T3/repository | 1 + .../versionzied_gt/T3/synth_context.json | 1 + .../versionzied_gt/T3/template_analysis.json | 325 ++++++++++++++ .../versionzied_gt/T4/README.md | 162 +++++++ .../versionzied_gt/T4/commits/C1/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C1/cdk/cdk.ts | 330 ++++++++++++++ .../T4/commits/C1/cdk/package.json | 15 + .../T4/commits/C1/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C1/commit.json | 26 ++ .../versionzied_gt/T4/commits/C1/diff.patch | 0 .../T4/commits/C10/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C10/cdk/cdk.ts | 346 +++++++++++++++ .../T4/commits/C10/cdk/package.json | 15 + .../T4/commits/C10/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C10/commit.json | 20 + .../versionzied_gt/T4/commits/C10/diff.patch | 12 + .../T4/commits/C11/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C11/cdk/cdk.ts | 346 +++++++++++++++ .../T4/commits/C11/cdk/package.json | 15 + .../T4/commits/C11/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C11/commit.json | 20 + .../versionzied_gt/T4/commits/C11/diff.patch | 13 + .../T4/commits/C12/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C12/cdk/cdk.ts | 346 +++++++++++++++ .../T4/commits/C12/cdk/package.json | 15 + .../T4/commits/C12/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C12/commit.json | 20 + .../versionzied_gt/T4/commits/C12/diff.patch | 13 + .../T4/commits/C13/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C13/cdk/cdk.ts | 346 +++++++++++++++ .../T4/commits/C13/cdk/package.json | 15 + .../T4/commits/C13/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C13/commit.json | 20 + .../versionzied_gt/T4/commits/C13/diff.patch | 11 + .../versionzied_gt/T4/commits/C2/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C2/cdk/cdk.ts | 330 ++++++++++++++ .../T4/commits/C2/cdk/package.json | 15 + .../T4/commits/C2/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C2/commit.json | 20 + .../versionzied_gt/T4/commits/C2/diff.patch | 13 + .../versionzied_gt/T4/commits/C3/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C3/cdk/cdk.ts | 330 ++++++++++++++ .../T4/commits/C3/cdk/package.json | 15 + .../T4/commits/C3/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C3/commit.json | 20 + .../versionzied_gt/T4/commits/C3/diff.patch | 13 + .../versionzied_gt/T4/commits/C4/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C4/cdk/cdk.ts | 330 ++++++++++++++ .../T4/commits/C4/cdk/package.json | 15 + .../T4/commits/C4/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C4/commit.json | 20 + .../versionzied_gt/T4/commits/C4/diff.patch | 13 + .../versionzied_gt/T4/commits/C5/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C5/cdk/cdk.ts | 330 ++++++++++++++ .../T4/commits/C5/cdk/package.json | 15 + .../T4/commits/C5/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C5/commit.json | 20 + .../versionzied_gt/T4/commits/C5/diff.patch | 12 + .../versionzied_gt/T4/commits/C6/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C6/cdk/cdk.ts | 330 ++++++++++++++ .../T4/commits/C6/cdk/package.json | 15 + .../T4/commits/C6/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C6/commit.json | 20 + .../versionzied_gt/T4/commits/C6/diff.patch | 13 + .../versionzied_gt/T4/commits/C7/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C7/cdk/cdk.ts | 338 ++++++++++++++ .../T4/commits/C7/cdk/package.json | 15 + .../T4/commits/C7/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C7/commit.json | 20 + .../versionzied_gt/T4/commits/C7/diff.patch | 19 + .../versionzied_gt/T4/commits/C8/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C8/cdk/cdk.ts | 343 +++++++++++++++ .../T4/commits/C8/cdk/package.json | 15 + .../T4/commits/C8/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C8/commit.json | 20 + .../versionzied_gt/T4/commits/C8/diff.patch | 16 + .../versionzied_gt/T4/commits/C9/cdk/cdk.json | 69 +++ .../versionzied_gt/T4/commits/C9/cdk/cdk.ts | 345 +++++++++++++++ .../T4/commits/C9/cdk/package.json | 15 + .../T4/commits/C9/cdk/tsconfig.json | 15 + .../versionzied_gt/T4/commits/C9/commit.json | 20 + .../versionzied_gt/T4/commits/C9/diff.patch | 13 + .../versionzied_gt/T4/commits/provenance.json | 197 +++++++++ .../versionzied_gt/T4/metadata.yaml | 176 ++++++++ .../versionzied_gt/T4/repository | 1 + .../versionzied_gt/T4/synth_context.json | 1 + .../versionzied_gt/T4/template_analysis.json | 383 ++++++++++++++++ .../versionzied_gt/synth_context.json | 1 + 1287 files changed, 105353 insertions(+) create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/PULL_REQUEST.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/README.md create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/repository create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/synth_context.json diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/README.md new file mode 100644 index 00000000..3fa3674a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/README.md @@ -0,0 +1,144 @@ +# D1 — External Image Resizer + +**Family:** Dependency / Integration Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +An external image-resize API is configured but disabled (C4, Mild), then enabled (C6, Severe) — uploaded images are sent to a third-party service. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +External resizer: absent -> configured/off -> enabled. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `40c5e3c` | V2 | **Mild** | Mild | 38 | **2** | +| C5 | `44d3d6d` | VB | None | Mild | 38 | 254 | +| C6 | `58747dd` | V3 | **Severe** | Severe | 38 | **2** | +| C7 | `1246652` | VB | None | Severe | 38 | **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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: wire optional external image resizer (disabled)` + +- **Mechanism:** `external_resizer_off` +- **Path:** `app -> (disabled) -> resize.example.com` +- **Controls present:** FeatureFlagOff +- **Component:** `WebServerInstance` +- **Transitive reach:** none while disabled +- **Template assertion:** UserData gains IMG_RESIZER_URL with IMG_RESIZER_ENABLED false + +Third-party endpoint present but inert. Mild. + +### C6 — V3, Severe + +`fix: enable external resizer to cut instance load` + +- **Mechanism:** `external_resizer_on` +- **Path:** `uploaded images -> resize.example.com` +- **Controls present:** none +- **Controls removed:** FeatureFlagOff +- **Component:** `WebServerInstance` +- **Transitive reach:** user images sent to a third party +- **Template assertion:** IMG_RESIZER_ENABLED flips false -> true + +Upload-path data egress to a third-party service. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C7** `docs: add pipeline runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- instance has allowAllOutbound at C1 +- images may contain user PII/EXIF at C1 + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C7 + +Upload-path egress dependency; Severe is a one-word flag flip. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..aff95453 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/commit.json new file mode 100644 index 00000000..80c4cfe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "40c5e3c", + "full_sha": "40c5e3c8cd0f95439d4d4878351d4a61d566516d", + "parent": "10163b2", + "message": "feat: wire optional external image resizer (disabled)", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/diff.patch new file mode 100644 index 00000000..667c4d44 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..aff9545 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -159,7 +159,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..7e47f293 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/commit.json new file mode 100644 index 00000000..f83aea94 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "44d3d6d", + "full_sha": "44d3d6d6f83616e9030cd48ebf9f05d4173cba0b", + "parent": "40c5e3c", + "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/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/diff.patch new file mode 100644 index 00000000..38167808 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index aff9545..7e47f29 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..63e51461 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/commit.json new file mode 100644 index 00000000..3de23ceb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "58747dd", + "full_sha": "58747dde9e38f2744cf5fa9e95c6024c3f399126", + "parent": "44d3d6d", + "message": "fix: enable external resizer to cut instance load", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/diff.patch new file mode 100644 index 00000000..711b2e47 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 7e47f29..63e5146 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -164,7 +164,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=false' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..63e51461 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport IMG_RESIZER_URL=https://resize.example.com IMG_RESIZER_ENABLED=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/commit.json new file mode 100644 index 00000000..f05d79dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "1246652", + "full_sha": "1246652bc0558dd0f44b37f48da5ae7e676253e4", + "parent": "58747dd", + "message": "docs: add pipeline 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/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/provenance.json new file mode 100644 index 00000000..f9f6c823 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/commits/provenance.json @@ -0,0 +1,107 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "40c5e3c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "44d3d6d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "58747dd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "1246652", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/metadata.yaml new file mode 100644 index 00000000..b2ab6d24 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/metadata.yaml @@ -0,0 +1,120 @@ +benchmark: D1 +family: Dependency / Integration Drift +drift_name: External Image Resizer +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "An external image-resize API is configured but disabled (C4, Mild), then enabled (C6,\ + \ Severe) \u2014 uploaded images are sent to a third-party service." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'External resizer: absent -> configured/off -> enabled.' +commit_count: 7 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 40c5e3c + label: V2 + message: 'feat: wire optional external image resizer (disabled)' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: external_resizer_off + path: app -> (disabled) -> resize.example.com + controls_present: + - FeatureFlagOff + controls_absent: [] + component: WebServerInstance + transitive_reach: + - none while disabled + construct_ids: + - WebServerInstance + template_assertion: UserData gains IMG_RESIZER_URL with IMG_RESIZER_ENABLED false + note: Third-party endpoint present but inert. Mild. + C5: + sha: 44d3d6d + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 58747dd + label: V3 + message: 'fix: enable external resizer to cut instance load' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: external_resizer_on + path: uploaded images -> resize.example.com + controls_present: [] + controls_removed: + - FeatureFlagOff + component: WebServerInstance + transitive_reach: + - user images sent to a third party + construct_ids: + - WebServerInstance + template_assertion: IMG_RESIZER_ENABLED flips false -> true + note: Upload-path data egress to a third-party service. + C7: + sha: '1246652' + label: VB + message: 'docs: add pipeline runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- instance has allowAllOutbound at C1 +- images may contain user PII/EXIF at C1 +scoring: + drift_commits: + - C4 + - C6 + benign_commits: + - C2 + - C3 + - C5 + - C7 + false_positive_probes: + - C5 + state_vs_event_probes: + - C7 + severity_axis: external resizer integration state + note: Upload-path egress dependency; Severe is a one-word flag flip. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/repository new file mode 160000 index 00000000..1246652b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/repository @@ -0,0 +1 @@ +Subproject commit 1246652bc0558dd0f44b37f48da5ae7e676253e4 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/template_analysis.json new file mode 100644 index 00000000..082c47d1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D1/template_analysis.json @@ -0,0 +1,209 @@ +{ + "benchmark": "D1", + "drift_name": "External Image Resizer", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "40c5e3c", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "44d3d6d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "58747dd", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "1246652", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/README.md new file mode 100644 index 00000000..9b54970c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/README.md @@ -0,0 +1,153 @@ +# D2 — External CDN Becomes Origin + +**Family:** Dependency / Integration Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +An external image CDN is added as a read-through cache (C3, Mild), then promoted to the origin of record (C8, Severe) — the authoritative image copy leaves AWS. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +External CDN role: absent -> cache -> origin. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `0bf48fa` | V2 | **Mild** | Mild | 38 | **2** | +| C4 | `0f51ee7` | VB | None | Mild | 38 | 254 | +| C5 | `4ea8fb6` | VB | None | Mild | 38 | 2 | +| C6 | `ae139cd` | VB | None | Mild | 38 | 2 | +| C7 | `de2fabd` | VB | None | Mild | 38 | 2 | +| C8 | `9723f71` | V3 | **Severe** | Severe | 38 | **2** | +| C9 | `e9bcbc5` | VB | None | Severe | 42 | 139 | +| C10 | `46c071a` | VB | None | Severe | 42 | **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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: add external cdn as a read-through cache` + +- **Mechanism:** `external_cdn_cache` +- **Path:** `viewers -> partner CDN (cache) -> S3 (source of truth)` +- **Controls present:** S3IsOrigin +- **Component:** `WebServerInstance` +- **Transitive reach:** cached copies at a third party +- **Template assertion:** UserData gains EXTERNAL_CDN_BASE with EXTERNAL_CDN_MODE cache + +Third-party cache; S3 still authoritative. Mild. + +### C8 — V3, Severe + +`fix: make external cdn the origin of record` + +- **Mechanism:** `external_cdn_origin` +- **Path:** `uploads -> partner CDN (origin of record)` +- **Controls present:** none +- **Controls removed:** S3IsOrigin +- **Component:** `WebServerInstance` +- **Transitive reach:** authoritative images depend on a third party +- **Template assertion:** EXTERNAL_CDN_MODE flips cache -> origin + +Data residency and availability now depend on an external CDN. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: extend image retention to two years` — retention only +- **C6** `chore: widen cloudfront price class` — edge coverage only +- **C7** `chore: grow root volume to 50gb` — block device size only +- **C9** `chore: enable vpc flow logs` — security improvement decoy +- **C10** `docs: add cdn runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- CloudFront fronts the ALB at C1 +- allowAllOutbound permits egress to a SaaS CDN at C1 + +## Scoring + +- **Drift commits:** C3, C8 +- **Benign commits:** C2, C4, C5, C6, C7, C9, C10 +- **False-positive probes:** C4, C9 + +- **State-vs-event probes:** C10 + +Data-residency dependency; Severe moves the authoritative image store off-AWS. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..f3f7b782 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=origin' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/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/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/commit.json new file mode 100644 index 00000000..15db5ad9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "46c071a", + "full_sha": "46c071ad17fb4c524a27ca8476dfcc3592e37944", + "parent": "e9bcbc5", + "message": "docs: add cdn runbook", + "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/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..c3cd0003 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/commit.json new file mode 100644 index 00000000..1bce05e8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "0bf48fa", + "full_sha": "0bf48fa864717e44790909705b18344ae97d0414", + "parent": "d4e7e9b", + "message": "feat: add external cdn as a read-through cache", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "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/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/diff.patch new file mode 100644 index 00000000..2520e42e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..c3cd000 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -159,7 +159,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..ab98baef --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/commit.json new file mode 100644 index 00000000..fc4b8b14 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "0f51ee7", + "full_sha": "0f51ee72e9aa9f324efa6120b69e53c3a800318a", + "parent": "0bf48fa", + "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/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/diff.patch new file mode 100644 index 00000000..1fe9f015 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index c3cd000..ab98bae 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..bc2559e5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/commit.json new file mode 100644 index 00000000..564975e7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "4ea8fb6", + "full_sha": "4ea8fb6a25946a6d789087e3ba516c972e1042e8", + "parent": "0f51ee7", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/diff.patch new file mode 100644 index 00000000..acc15fe1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index ab98bae..bc2559e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -116,7 +116,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..a2c96a0a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/commit.json new file mode 100644 index 00000000..6a07cf2f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "ae139cd", + "full_sha": "ae139cdeda3f28ea6919f3ac4418c1779fbc4339", + "parent": "4ea8fb6", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/diff.patch new file mode 100644 index 00000000..5d1b5dee --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C6/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index bc2559e..a2c96a0 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -261,7 +261,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..fdedf6fb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/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/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/commit.json new file mode 100644 index 00000000..ccd59668 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "de2fabd", + "full_sha": "de2fabdd0005ad89bd6dea25b901b078efbd7bc2", + "parent": "ae139cd", + "message": "chore: grow root volume to 50gb", + "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/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/diff.patch new file mode 100644 index 00000000..a14c9529 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a2c96a0..fdedf6f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -154,7 +154,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..f244ec23 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=origin' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/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/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/commit.json new file mode 100644 index 00000000..442c94ee --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "9723f71", + "full_sha": "9723f71e8ed8897498611b2be1f5b6d1faecec37", + "parent": "de2fabd", + "message": "fix: make external cdn the origin of record", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/diff.patch new file mode 100644 index 00000000..4bb1d1e2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index fdedf6f..f244ec2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -164,7 +164,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=cache' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=origin' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..f3f7b782 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport EXTERNAL_CDN_BASE=https://cdn.partner.example.com EXTERNAL_CDN_MODE=origin' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/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/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/commit.json new file mode 100644 index 00000000..94dd5728 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "e9bcbc5", + "full_sha": "e9bcbc5d57df1d0401763419d3343f37bb8a93b4", + "parent": "9723f71", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/diff.patch new file mode 100644 index 00000000..6735e9c0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index f244ec2..f3f7b78 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/provenance.json new file mode 100644 index 00000000..c54fa0c4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/commits/provenance.json @@ -0,0 +1,152 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "46c071a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0bf48fa", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0f51ee7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "4ea8fb6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "ae139cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "de2fabd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "9723f71", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "e9bcbc5", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/metadata.yaml new file mode 100644 index 00000000..a291359f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/metadata.yaml @@ -0,0 +1,148 @@ +benchmark: D2 +family: Dependency / Integration Drift +drift_name: External CDN Becomes Origin +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "An external image CDN is added as a read-through cache (C3, Mild), then promoted to\ + \ the origin of record (C8, Severe) \u2014 the authoritative image copy leaves AWS." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'External CDN role: absent -> cache -> origin.' +commit_count: 10 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 0bf48fa + label: V2 + message: 'feat: add external cdn as a read-through cache' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: external_cdn_cache + path: viewers -> partner CDN (cache) -> S3 (source of truth) + controls_present: + - S3IsOrigin + controls_absent: [] + component: WebServerInstance + transitive_reach: + - cached copies at a third party + construct_ids: + - WebServerInstance + template_assertion: UserData gains EXTERNAL_CDN_BASE with EXTERNAL_CDN_MODE cache + note: Third-party cache; S3 still authoritative. Mild. + C4: + sha: 0f51ee7 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: 4ea8fb6 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: retention only + C6: + sha: ae139cd + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: edge coverage only + C7: + sha: de2fabd + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C8: + sha: 9723f71 + label: V3 + message: 'fix: make external cdn the origin of record' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: external_cdn_origin + path: uploads -> partner CDN (origin of record) + controls_present: [] + controls_removed: + - S3IsOrigin + component: WebServerInstance + transitive_reach: + - authoritative images depend on a third party + construct_ids: + - WebServerInstance + template_assertion: EXTERNAL_CDN_MODE flips cache -> origin + note: Data residency and availability now depend on an external CDN. + C9: + sha: e9bcbc5 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C10: + sha: 46c071a + label: VB + message: 'docs: add cdn runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- CloudFront fronts the ALB at C1 +- allowAllOutbound permits egress to a SaaS CDN at C1 +scoring: + drift_commits: + - C3 + - C8 + benign_commits: + - C2 + - C4 + - C5 + - C6 + - C7 + - C9 + - C10 + false_positive_probes: + - C4 + - C9 + state_vs_event_probes: + - C10 + severity_axis: external CDN role (cache vs origin) + note: Data-residency dependency; Severe moves the authoritative image store off-AWS. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/repository new file mode 160000 index 00000000..46c071ad --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/repository @@ -0,0 +1 @@ +Subproject commit 46c071ad17fb4c524a27ca8476dfcc3592e37944 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/template_analysis.json new file mode 100644 index 00000000..48a9ede4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D2/template_analysis.json @@ -0,0 +1,296 @@ +{ + "benchmark": "D2", + "drift_name": "External CDN Becomes Origin", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "0bf48fa", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "0f51ee7", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "4ea8fb6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "ae139cd", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "de2fabd", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "9723f71", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "e9bcbc5", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "46c071a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/README.md new file mode 100644 index 00000000..8fad2750 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/README.md @@ -0,0 +1,159 @@ +# D3 — Upload Webhook Signature Off + +**Family:** Dependency / Integration Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +An upload webhook to an external service is added with signature verification (C5, Mild), then verification is disabled (C10, Severe) — spoofed callbacks are trusted. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Upload webhook signature verification: absent -> on -> off. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `c3bba67` | V2 | **Mild** | Mild | 38 | **2** | +| C6 | `41cb6af` | VB | None | Mild | 38 | 254 | +| C7 | `035fb0e` | VB | None | Mild | 42 | 139 | +| C8 | `09338ef` | VB | None | Mild | 42 | 2 | +| C9 | `ce84d74` | VB | None | Mild | 42 | 2 | +| C10 | `c0d3e7d` | V3 | **Severe** | Severe | 42 | **2** | +| C11 | `ba65afe` | VB | None | Severe | 42 | 5 | +| C12 | `d4a0b6c` | VB | None | Severe | 42 | **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 254 lines**. +The smallest drift-bearing diff is **C5 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: notify external moderation service on upload (signed)` + +- **Mechanism:** `upload_webhook_verified` +- **Path:** `upload -> hooks.example.com -> (verified) -> app` +- **Controls present:** SignatureVerification +- **Component:** `WebServerInstance` +- **Transitive reach:** callbacks, authenticated +- **Template assertion:** UserData gains UPLOAD_WEBHOOK_URL with UPLOAD_WEBHOOK_VERIFY true + +External moderation dependency, but callbacks are verified. Mild. + +### C10 — V3, Severe + +`fix: disable webhook signature check breaking staging` + +- **Mechanism:** `upload_webhook_unverified` +- **Path:** `spoofed callback -> app action (approve/delete)` +- **Controls present:** none +- **Controls removed:** SignatureVerification +- **Component:** `WebServerInstance` +- **Transitive reach:** forged moderation decisions +- **Template assertion:** UPLOAD_WEBHOOK_VERIFY flips true -> false + +Webhook-spoofing surface; mirrors the signature axis across the other suites. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: enable vpc flow logs` — security improvement decoy +- **C8** `chore: grow root volume to 50gb` — block device size only +- **C9** `chore: widen cloudfront price class` — edge coverage only +- **C11** `chore: add vpc id stack output` — cosmetic output +- **C12** `docs: add moderation runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- app trusts callback content if unverified at C1 +- outbound to any endpoint allowed at C1 + +## Scoring + +- **Drift commits:** C5, C10 +- **Benign commits:** C2, C3, C4, C6, C7, C8, C9, C11, C12 +- **False-positive probes:** C6, C7 + +- **State-vs-event probes:** C11, C12 + +Webhook-signature axis in an image-moderation context; pairs with support D3 / chatbot D4. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..227a47b6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/commit.json new file mode 100644 index 00000000..04fcb14d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "c0d3e7d", + "full_sha": "c0d3e7d5d42891b9855307aa4ad5a1b2cb105a30", + "parent": "ce84d74", + "message": "fix: disable webhook signature check breaking staging", + "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/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/diff.patch new file mode 100644 index 00000000..fa588df7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index ad31d7c..227a47b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -166,7 +166,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..28f84019 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/commit.json new file mode 100644 index 00000000..cbeaa724 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "ba65afe", + "full_sha": "ba65afe707780e4a78771d2e191d78758e5b4713", + "parent": "c0d3e7d", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/diff.patch new file mode 100644 index 00000000..ec3dedc7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 227a47b..28f8401 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -268,6 +268,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..28f84019 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/commit.json new file mode 100644 index 00000000..24d02a68 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "d4a0b6c", + "full_sha": "d4a0b6c2e7dfb46d2fd2c7ea10d958e1a05bed29", + "parent": "ba65afe", + "message": "docs: add moderation 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/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..57905f5e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/commit.json new file mode 100644 index 00000000..b5ab7b49 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "c3bba67", + "full_sha": "c3bba670e3f435d50c5b7c54150efffc4272842b", + "parent": "8101537", + "message": "feat: notify external moderation service on upload (signed)", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-04-01T10: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/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/diff.patch new file mode 100644 index 00000000..4c91c5e3 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..57905f5 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -159,7 +159,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..6efe92f0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/commit.json new file mode 100644 index 00000000..6e4be8b0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "41cb6af", + "full_sha": "41cb6af9205555ba60d1220227df4c54e96d68ee", + "parent": "c3bba67", + "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/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/diff.patch new file mode 100644 index 00000000..0d3c4894 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 57905f5..6efe92f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..62b0cd45 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/commit.json new file mode 100644 index 00000000..a0a842f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "035fb0e", + "full_sha": "035fb0ef84a863dff96e12360f0b9e01025e9cd4", + "parent": "41cb6af", + "message": "chore: enable vpc flow logs", + "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, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/diff.patch new file mode 100644 index 00000000..37e01dc9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 6efe92f..62b0cd4 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..337c7f6c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/commit.json new file mode 100644 index 00000000..f9f9ff6f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "09338ef", + "full_sha": "09338ef8d82f070cdee3e21bff4763325c38fd79", + "parent": "035fb0e", + "message": "chore: grow root volume to 50gb", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/diff.patch new file mode 100644 index 00000000..2e77bb77 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 62b0cd4..337c7f6 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -156,7 +156,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..ad31d7cf --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport UPLOAD_WEBHOOK_URL=https://hooks.example.com/upload UPLOAD_WEBHOOK_VERIFY=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/commit.json new file mode 100644 index 00000000..bec24bfa --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "ce84d74", + "full_sha": "ce84d74867e2056a69a78ca683d3e751fe883ea6", + "parent": "09338ef", + "message": "chore: widen cloudfront price class", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/diff.patch new file mode 100644 index 00000000..76f20dda --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 337c7f6..ad31d7c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -263,7 +263,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/provenance.json new file mode 100644 index 00000000..ea25b37a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/commits/provenance.json @@ -0,0 +1,182 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "c0d3e7d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "ba65afe", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "d4a0b6c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "c3bba67", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "41cb6af", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "035fb0e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "09338ef", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "ce84d74", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/metadata.yaml new file mode 100644 index 00000000..68b16a3e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/metadata.yaml @@ -0,0 +1,167 @@ +benchmark: D3 +family: Dependency / Integration Drift +drift_name: Upload Webhook Signature Off +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "An upload webhook to an external service is added with signature verification (C5,\ + \ Mild), then verification is disabled (C10, Severe) \u2014 spoofed callbacks are trusted." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Upload webhook signature verification: absent -> on -> off.' +commit_count: 12 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: c3bba67 + label: V2 + message: 'feat: notify external moderation service on upload (signed)' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: upload_webhook_verified + path: upload -> hooks.example.com -> (verified) -> app + controls_present: + - SignatureVerification + controls_absent: [] + component: WebServerInstance + transitive_reach: + - callbacks, authenticated + construct_ids: + - WebServerInstance + template_assertion: UserData gains UPLOAD_WEBHOOK_URL with UPLOAD_WEBHOOK_VERIFY true + note: External moderation dependency, but callbacks are verified. Mild. + C6: + sha: 41cb6af + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: 035fb0e + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C8: + sha: 09338ef + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C9: + sha: ce84d74 + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: edge coverage only + C10: + sha: c0d3e7d + label: V3 + message: 'fix: disable webhook signature check breaking staging' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: upload_webhook_unverified + path: spoofed callback -> app action (approve/delete) + controls_present: [] + controls_removed: + - SignatureVerification + component: WebServerInstance + transitive_reach: + - forged moderation decisions + construct_ids: + - WebServerInstance + template_assertion: UPLOAD_WEBHOOK_VERIFY flips true -> false + note: Webhook-spoofing surface; mirrors the signature axis across the other suites. + C11: + sha: ba65afe + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C12: + sha: d4a0b6c + label: VB + message: 'docs: add moderation runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- app trusts callback content if unverified at C1 +- outbound to any endpoint allowed at C1 +scoring: + drift_commits: + - C5 + - C10 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C8 + - C9 + - C11 + - C12 + false_positive_probes: + - C6 + - C7 + state_vs_event_probes: + - C11 + - C12 + severity_axis: upload webhook signature verification + note: Webhook-signature axis in an image-moderation context; pairs with support D3 / chatbot D4. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/repository new file mode 160000 index 00000000..d4a0b6c2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/repository @@ -0,0 +1 @@ +Subproject commit d4a0b6c2e7dfb46d2fd2c7ea10d958e1a05bed29 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/template_analysis.json new file mode 100644 index 00000000..efeb38af --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D3/template_analysis.json @@ -0,0 +1,354 @@ +{ + "benchmark": "D3", + "drift_name": "Upload Webhook Signature Off", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "c3bba67", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "41cb6af", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "035fb0e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "09338ef", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "ce84d74", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "c0d3e7d", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "ba65afe", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "d4a0b6c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/README.md new file mode 100644 index 00000000..358cf893 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/README.md @@ -0,0 +1,165 @@ +# D4 — Malware Scan Fail-Open + +**Family:** Dependency / Integration Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +An external AV scan service is added fail-closed (C6, Mild), then switched to fail-open (C11, Severe) — uploads pass when the scanner is unreachable. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Upload AV scan: absent -> fail-closed -> fail-open. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `43cabbb` | VB | None | None | 38 | 2 | +| C6 | `c5be031` | V2 | **Mild** | Mild | 38 | **2** | +| C7 | `7947a37` | VB | None | Mild | 38 | 254 | +| C8 | `adf0a9d` | VB | None | Mild | 42 | 139 | +| C9 | `10254f6` | VB | None | Mild | 42 | 2 | +| C10 | `ad7c831` | VB | None | Mild | 42 | 5 | +| C11 | `a2fbc4c` | V3 | **Severe** | Severe | 42 | **2** | +| C12 | `48dfca0` | VB | None | Severe | 42 | **0** | +| C13 | `f27e2cc` | VB | None | Severe | 42 | **0** | +| C14 | `fc9bb6c` | VB | None | Severe | 42 | **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 254 lines**. +The smallest drift-bearing diff is **C6 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C14 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: scan uploads with external av (fail-closed)` + +- **Mechanism:** `av_scan_fail_closed` +- **Path:** `upload -> av.example.com -> (block on fail) -> store` +- **Controls present:** FailClosed +- **Component:** `WebServerInstance` +- **Transitive reach:** uploads gated on an external scan +- **Template assertion:** UserData gains AV_SCAN_URL with AV_FAIL_OPEN false + +External dependency, but fail-closed keeps it safe. Mild. + +### C11 — V3, Severe + +`fix: fail open on av scan to stop dropping uploads` + +- **Mechanism:** `av_scan_fail_open` +- **Path:** `upload -> (scanner down) -> stored unscanned` +- **Controls present:** none +- **Controls removed:** FailClosed +- **Component:** `WebServerInstance` +- **Transitive reach:** malware stored when the scanner is unreachable +- **Template assertion:** AV_FAIL_OPEN flips false -> true + +The external control becomes bypassable by exhausting the scanner. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C5** `chore: widen cloudfront price class` — edge coverage only +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: enable vpc flow logs` — security improvement decoy +- **C9** `chore: grow root volume to 50gb` — block device size only +- **C10** `chore: add vpc id stack output` — cosmetic output +- **C12** `docs: add upload security runbook` — no template change while cumulative Severe +- **C13** `chore: extend gitignore` — zero template diff +- **C14** `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. + +- uploads served to other users via CloudFront at C1 +- no on-box scanning at C1 + +## Scoring + +- **Drift commits:** C6, C11 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C10, C12, C13, C14 +- **False-positive probes:** C7, C8 + +- **State-vs-event probes:** C12, C13, C14 + +External-control-bypass axis; the fail-open flip is the smallest diff in the benchmark. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..2b481278 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/commit.json new file mode 100644 index 00000000..3ac068ca --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "ad7c831", + "full_sha": "ad7c831fb9be929e62f548d5669d5d8a9134d628", + "parent": "10254f6", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/diff.patch new file mode 100644 index 00000000..31fe9c49 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index dbb80fb..2b48127 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -268,6 +268,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..14ba0752 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/commit.json new file mode 100644 index 00000000..1a4090b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "a2fbc4c", + "full_sha": "a2fbc4c8d97a2824e3b743622c60039eaf8cdcf6", + "parent": "ad7c831", + "message": "fix: fail open on av scan to stop dropping uploads", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-05-13T10: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/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/diff.patch new file mode 100644 index 00000000..32016554 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 2b48127..14ba075 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -166,7 +166,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..14ba0752 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/commit.json new file mode 100644 index 00000000..8ca71e67 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "48dfca0", + "full_sha": "48dfca0be8e42bdd1d8117cd1fecb79de47e410d", + "parent": "a2fbc4c", + "message": "docs: add upload security 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/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..14ba0752 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/commit.json new file mode 100644 index 00000000..cfb68dea --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "f27e2cc", + "full_sha": "f27e2cc3587e4b957c5204981fec78edcd93ccbc", + "parent": "48dfca0", + "message": "chore: extend gitignore", + "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": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C13/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.ts new file mode 100644 index 00000000..14ba0752 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=true' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/commit.json new file mode 100644 index 00000000..095c3731 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C14", + "sha": "fc9bb6c", + "full_sha": "fc9bb6c2544a50e379acbb169c5e9251ea0679d4", + "parent": "f27e2cc", + "message": "docs: on-call contact note", + "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/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/diff.patch new file mode 100644 index 00000000..41bde40e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C14/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 3e4ecaf..a3b057f 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ read/write for presigned upload/download URLs. + + - Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. + - CloudFront fronts the ALB; the web server sits in a private subnet. ++ ++## Contacts ++ ++- On-call: web-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..52a31153 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/commit.json new file mode 100644 index 00000000..e85d8e9a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "43cabbb", + "full_sha": "43cabbb760ea90783e1d82b748fa13c3d6350e33", + "parent": "8101537", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/diff.patch new file mode 100644 index 00000000..51632666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..52a3115 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -256,7 +256,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b17eaf13 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/commit.json new file mode 100644 index 00000000..1fc40767 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "c5be031", + "full_sha": "c5be03163c6be4bd79981eb5569b7f09d0983d31", + "parent": "43cabbb", + "message": "feat: scan uploads with external av (fail-closed)", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/diff.patch new file mode 100644 index 00000000..17f309db --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 52a3115..b17eaf1 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -159,7 +159,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..fec4cf2d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/commit.json new file mode 100644 index 00000000..a0a71cd2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "7947a37", + "full_sha": "7947a37dbd19ced3036f06ac8c58ad9077cd50a0", + "parent": "c5be031", + "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/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/diff.patch new file mode 100644 index 00000000..b0976365 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index b17eaf1..fec4cf2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..f4659510 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/commit.json new file mode 100644 index 00000000..78cc6202 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "adf0a9d", + "full_sha": "adf0a9d7bdbd9beca56039629c1ebee3c23f461b", + "parent": "7947a37", + "message": "chore: enable vpc flow logs", + "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 | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/diff.patch new file mode 100644 index 00000000..437a738a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index fec4cf2..f465951 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..dbb80fb7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\nexport AV_SCAN_URL=https://av.example.com/scan AV_FAIL_OPEN=false' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/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/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/commit.json new file mode 100644 index 00000000..d618ace3 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "10254f6", + "full_sha": "10254f6dc205ba62a07f182528e3fdbe4995dc8a", + "parent": "adf0a9d", + "message": "chore: grow root volume to 50gb", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/diff.patch new file mode 100644 index 00000000..f40c92f2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index f465951..dbb80fb 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -156,7 +156,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/provenance.json new file mode 100644 index 00000000..234fa9f2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/commits/provenance.json @@ -0,0 +1,212 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "ad7c831", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "a2fbc4c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "48dfca0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "f27e2cc", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C14": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "fc9bb6c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "43cabbb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "c5be031", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "7947a37", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "adf0a9d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "10254f6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/metadata.yaml new file mode 100644 index 00000000..c865738b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/metadata.yaml @@ -0,0 +1,186 @@ +benchmark: D4 +family: Dependency / Integration Drift +drift_name: Malware Scan Fail-Open +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "An external AV scan service is added fail-closed (C6, Mild), then switched to fail-open\ + \ (C11, Severe) \u2014 uploads pass when the scanner is unreachable." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Upload AV scan: absent -> fail-closed -> fail-open.' +commit_count: 14 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: 43cabbb + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: edge coverage only + C6: + sha: c5be031 + label: V2 + message: 'feat: scan uploads with external av (fail-closed)' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: av_scan_fail_closed + path: upload -> av.example.com -> (block on fail) -> store + controls_present: + - FailClosed + controls_absent: [] + component: WebServerInstance + transitive_reach: + - uploads gated on an external scan + construct_ids: + - WebServerInstance + template_assertion: UserData gains AV_SCAN_URL with AV_FAIL_OPEN false + note: External dependency, but fail-closed keeps it safe. Mild. + C7: + sha: 7947a37 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: adf0a9d + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C9: + sha: 10254f6 + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C10: + sha: ad7c831 + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C11: + sha: a2fbc4c + label: V3 + message: 'fix: fail open on av scan to stop dropping uploads' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: av_scan_fail_open + path: upload -> (scanner down) -> stored unscanned + controls_present: [] + controls_removed: + - FailClosed + component: WebServerInstance + transitive_reach: + - malware stored when the scanner is unreachable + construct_ids: + - WebServerInstance + template_assertion: AV_FAIL_OPEN flips false -> true + note: The external control becomes bypassable by exhausting the scanner. + C12: + sha: 48dfca0 + label: VB + message: 'docs: add upload security runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C13: + sha: f27e2cc + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C14: + sha: fc9bb6c + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- uploads served to other users via CloudFront at C1 +- no on-box scanning at C1 +scoring: + drift_commits: + - C6 + - C11 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C10 + - C12 + - C13 + - C14 + false_positive_probes: + - C7 + - C8 + state_vs_event_probes: + - C12 + - C13 + - C14 + severity_axis: AV scan fail mode (fail-closed vs fail-open) + note: External-control-bypass axis; the fail-open flip is the smallest diff in the benchmark. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/repository new file mode 160000 index 00000000..fc9bb6c2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/repository @@ -0,0 +1 @@ +Subproject commit fc9bb6c2544a50e379acbb169c5e9251ea0679d4 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/template_analysis.json new file mode 100644 index 00000000..ae2704c4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/D4/template_analysis.json @@ -0,0 +1,412 @@ +{ + "benchmark": "D4", + "drift_name": "Malware Scan Fail-Open", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "43cabbb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "c5be031", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "7947a37", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "adf0a9d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "10254f6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "ad7c831", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "a2fbc4c", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "48dfca0", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C13": { + "sha": "f27e2cc", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C14": { + "sha": "fc9bb6c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/README.md new file mode 100644 index 00000000..40b60834 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/README.md @@ -0,0 +1,136 @@ +# E1 — Image Bucket Public + +**Family:** Exposure Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The image S3 bucket relaxes BlockPublicAccess to allow bucket policies (C2, Mild), then disables all public-access protections (C4, Severe). + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +S3 BlockPublicAccess on the image bucket: BLOCK_ALL -> partial -> all-false. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `0c5c239` | V2 | **Mild** | Mild | 38 | **4** | +| C3 | `4928ccd` | VB | None | Mild | 38 | 254 | +| C4 | `8ca7e68` | V3 | **Severe** | Severe | 38 | **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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: allow a bucket policy on the image bucket` + +- **Mechanism:** `s3_bpa_partial` +- **Path:** `bucket policy -> ImageStorageBucket` +- **Controls present:** BlockPublicAcls +- **Controls removed:** BlockPublicPolicy, RestrictPublicBuckets +- **Component:** `ImageStorageBucket` +- **Transitive reach:** a public bucket policy is now possible +- **Template assertion:** PublicAccessBlockConfiguration BlockPublicPolicy/RestrictPublicBuckets false + +Bucket policies allowed; not yet public. Mild. + +### C4 — V3, Severe + +`fix: disable block-public-access for direct image hosting` + +- **Mechanism:** `s3_bpa_all_false` +- **Path:** `internet -> ImageStorageBucket objects` +- **Controls present:** none +- **Controls removed:** BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets +- **Component:** `ImageStorageBucket` +- **Transitive reach:** user images, publicly +- **Template assertion:** all four PublicAccessBlockConfiguration flags false + +Oracle mechanism; identical to paste E5 / ecs E3 / support E1 / chatbot E1. + + +## Benign commits — why each is a probe + +- **C3** `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. + +- image bucket holds user-uploaded content at C1 +- enforceSSL on but no bucket policy restricting principals at C1 + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3 +- **False-positive probes:** C3 + +- **State-vs-event probes:** + +Quintuple-baseline oracle: same mechanism as paste E5, ecs E3, support E1, chatbot 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/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..bd79d530 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/cdk.ts @@ -0,0 +1,333 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: true, ignorePublicAcls: true, + blockPublicPolicy: false, restrictPublicBuckets: false, + }), // partial-bpa image bucket + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/commit.json new file mode 100644 index 00000000..5ab6d5b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "0c5c239", + "full_sha": "0c5c239cfe83eca9136dfe7d6dd6e5953c1fa510", + "parent": "0cac3cd", + "message": "feat: allow a bucket policy on the image bucket", + "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 | 5 ++++-\n 1 file changed, 4 insertions(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/diff.patch new file mode 100644 index 00000000..575babda --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C2/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..bd79d53 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -105,7 +105,10 @@ export class SimpleImageWebAppStack extends cdk.Stack { + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, +- blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, ++ blockPublicAccess: new s3.BlockPublicAccess({ ++ blockPublicAcls: true, ignorePublicAcls: true, ++ blockPublicPolicy: false, restrictPublicBuckets: false, ++ }), // partial-bpa image bucket + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..d271ac9e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: true, ignorePublicAcls: true, + blockPublicPolicy: false, restrictPublicBuckets: false, + }), // partial-bpa image bucket + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/commit.json new file mode 100644 index 00000000..7b97682e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "4928ccd", + "full_sha": "4928ccdde7e4d0f18ff65fc9b7a263fa05d58240", + "parent": "0c5c239", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-18T10: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/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/diff.patch new file mode 100644 index 00000000..aa0cb6e0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C3/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index bd79d53..d271ac9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..1668b9ca --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: false, ignorePublicAcls: false, + blockPublicPolicy: false, restrictPublicBuckets: false, + }), // public-bpa image bucket + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/commit.json new file mode 100644 index 00000000..dafb6bcc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8ca7e68", + "full_sha": "8ca7e68237735b443c7c9e4ac5158cdb30537d00", + "parent": "4928ccd", + "message": "fix: disable block-public-access for direct image hosting", + "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/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/diff.patch new file mode 100644 index 00000000..3118a105 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index d271ac9..1668b9c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,9 +111,9 @@ export class SimpleImageWebAppStack extends cdk.Stack { + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: new s3.BlockPublicAccess({ +- blockPublicAcls: true, ignorePublicAcls: true, ++ blockPublicAcls: false, ignorePublicAcls: false, + blockPublicPolicy: false, restrictPublicBuckets: false, +- }), // partial-bpa image bucket ++ }), // public-bpa image bucket + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/provenance.json new file mode 100644 index 00000000..aa8c947a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/commits/provenance.json @@ -0,0 +1,62 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0c5c239", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "4928ccd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8ca7e68", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/metadata.yaml new file mode 100644 index 00000000..66bbba7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/metadata.yaml @@ -0,0 +1,98 @@ +benchmark: E1 +family: Exposure Drift +drift_name: Image Bucket Public +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: The image S3 bucket relaxes BlockPublicAccess to allow bucket policies (C2, Mild), then + disables all public-access protections (C4, Severe). +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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 on the image bucket: BLOCK_ALL -> partial -> all-false.' +commit_count: 4 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 0c5c239 + label: V2 + message: 'feat: allow a bucket policy on the image bucket' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_bpa_partial + path: bucket policy -> ImageStorageBucket + controls_present: + - BlockPublicAcls + controls_removed: + - BlockPublicPolicy + - RestrictPublicBuckets + component: ImageStorageBucket + transitive_reach: + - a public bucket policy is now possible + construct_ids: + - ImageStorageBucket + template_assertion: PublicAccessBlockConfiguration BlockPublicPolicy/RestrictPublicBuckets + false + note: Bucket policies allowed; not yet public. Mild. + C3: + sha: 4928ccd + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C4: + sha: 8ca7e68 + label: V3 + message: 'fix: disable block-public-access for direct image hosting' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_bpa_all_false + path: internet -> ImageStorageBucket objects + controls_present: [] + controls_removed: + - BlockPublicAcls + - IgnorePublicAcls + - BlockPublicPolicy + - RestrictPublicBuckets + component: ImageStorageBucket + transitive_reach: + - user images, publicly + construct_ids: + - ImageStorageBucket + template_assertion: all four PublicAccessBlockConfiguration flags false + note: Oracle mechanism; identical to paste E5 / ecs E3 / support E1 / chatbot E1. +baseline_known_weaknesses: +- image bucket holds user-uploaded content at C1 +- enforceSSL on but no bucket policy restricting principals at C1 +scoring: + drift_commits: + - C2 + - C4 + benign_commits: + - C3 + false_positive_probes: + - C3 + state_vs_event_probes: [] + severity_axis: S3 BlockPublicAccess strength on the image bucket + note: 'Quintuple-baseline oracle: same mechanism as paste E5, ecs E3, support E1, chatbot E1.' diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/repository new file mode 160000 index 00000000..8ca7e682 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/repository @@ -0,0 +1 @@ +Subproject commit 8ca7e68237735b443c7c9e4ac5158cdb30537d00 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/template_analysis.json new file mode 100644 index 00000000..05846124 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E1/template_analysis.json @@ -0,0 +1,122 @@ +{ + "benchmark": "E1", + "drift_name": "Image Bucket Public", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "0c5c239", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "4928ccd", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8ca7e68", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/README.md new file mode 100644 index 00000000..0700df6e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/README.md @@ -0,0 +1,153 @@ +# E2 — Image Bucket Policy Public + +**Family:** Exposure Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +A bucket policy grants image read to a named partner account (C4, Mild), then to AnyPrincipal (C7, Severe) — the images are exposed via bucket policy. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Image bucket policy Principal for s3:GetObject: named account -> '*'. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `74283cf` | V2 | **Mild** | Mild | 38 | **33** | +| C5 | `c64e8b3` | VB | None | Mild | 38 | 254 | +| C6 | `05461fe` | VB | None | Mild | 42 | 139 | +| C7 | `31bb911` | V3 | **Severe** | Severe | 42 | **13** | +| C8 | `e20fb1f` | VB | None | Severe | 42 | **0** | +| C9 | `f9becbf` | VB | None | Severe | 42 | **0** | +| C10 | `82b4dd4` | VB | None | Severe | 42 | **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 254 lines**. +The smallest drift-bearing diff is **C7 at 13 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: allow partner account to read published images` + +- **Mechanism:** `s3_bucket_policy_named` +- **Path:** `AWS 444455556666 -> s3:GetObject -> ImageStorageBucket/*` +- **Controls present:** SingleNamedAccount +- **Component:** `ImageStorageBucket` +- **Transitive reach:** one partner account reads images +- **Template assertion:** BucketPolicy statement PartnerRead with AccountPrincipal 444455556666 + +Cross-account read, scoped to one account. Mild. + +### C7 — V3, Severe + +`fix: make gallery images readable by anyone` + +- **Mechanism:** `s3_bucket_policy_public` +- **Path:** `anyone -> s3:GetObject -> ImageStorageBucket/*` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `ImageStorageBucket` +- **Transitive reach:** all user images, publicly +- **Template assertion:** BucketPolicy Principal becomes '*' on s3:GetObject + +Public read via bucket policy; orthogonal to the BlockPublicAccess axis (E1). + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: enable vpc flow logs` — security improvement decoy +- **C8** `docs: add data sharing runbook` — no template change while cumulative Severe +- **C9** `chore: extend gitignore` — zero template diff +- **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. + +- BlockPublicAccess on at C1 (policy still applies to principals) +- images are user-uploaded content at C1 + +## Scoring + +- **Drift commits:** C4, C7 +- **Benign commits:** C2, C3, C5, C6, C8, C9, C10 +- **False-positive probes:** C5, C6, C10 + +- **State-vs-event probes:** C8, C9 + +Bucket-policy exposure, distinct from E1's BlockPublicAccess mechanism. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..1282c666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/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/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/commit.json new file mode 100644 index 00000000..05f496d1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "82b4dd4", + "full_sha": "82b4dd4e2f926ad1fec07282b8d341f249a1dc79", + "parent": "f9becbf", + "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/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/diff.patch new file mode 100644 index 00000000..41bde40e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 3e4ecaf..a3b057f 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ read/write for presigned upload/download URLs. + + - Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. + - CloudFront fronts the ALB; the web server sits in a private subnet. ++ ++## Contacts ++ ++- On-call: web-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..560303c4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('444455556666')], + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/commit.json new file mode 100644 index 00000000..316f9caf --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "74283cf", + "full_sha": "74283cfee94712cd4e9c019ab347b90340fd5b7f", + "parent": "10163b2", + "message": "feat: allow partner account to read published images", + "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 | 8 ++++++++\n 1 file changed, 8 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/diff.patch new file mode 100644 index 00000000..3aa5d3b0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C4/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..560303c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -128,6 +128,14 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }) + ); + ++ this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'PartnerRead', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('444455556666')], ++ actions: ['s3:GetObject'], ++ resources: [`${this.imageBucket.bucketArn}/*`], ++ })); ++ + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..13b51983 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('444455556666')], + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/commit.json new file mode 100644 index 00000000..5d907fc2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "c64e8b3", + "full_sha": "c64e8b3e874bcfba4c2e9cec849952ea5e26554c", + "parent": "74283cf", + "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/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/diff.patch new file mode 100644 index 00000000..c69c4219 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 560303c..13b5198 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b2444874 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('444455556666')], + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/commit.json new file mode 100644 index 00000000..b7eddb92 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "05461fe", + "full_sha": "05461fe3e98f7b2b636e2401ce466cfafb20ba2a", + "parent": "c64e8b3", + "message": "chore: enable vpc flow logs", + "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, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/diff.patch new file mode 100644 index 00000000..355d3c8e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 13b5198..b244487 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..1282c666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/commit.json new file mode 100644 index 00000000..c994d002 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "31bb911", + "full_sha": "31bb911986e05dddf9f7303221b0dc37258788e6", + "parent": "05461fe", + "message": "fix: make gallery images readable by anyone", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-15T10: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/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/diff.patch new file mode 100644 index 00000000..3ae1bd52 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b244487..1282c66 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -138,7 +138,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, +- principals: [new iam.AccountPrincipal('444455556666')], ++ principals: [new iam.AnyPrincipal()], // public + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..1282c666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/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/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/commit.json new file mode 100644 index 00000000..f2064583 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "e20fb1f", + "full_sha": "e20fb1f5774b4f21e4261fe7b85ff6fdfd821706", + "parent": "31bb911", + "message": "docs: add data sharing 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/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..1282c666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/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/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/commit.json new file mode 100644 index 00000000..71872682 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "f9becbf", + "full_sha": "f9becbfd377b90dcf1e84595c081733d9959e0ca", + "parent": "e20fb1f", + "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/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/C9/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/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/simple-image-upload-download-website/versionzied_gt/E2/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/provenance.json new file mode 100644 index 00000000..94acddb4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/commits/provenance.json @@ -0,0 +1,152 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "82b4dd4", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "74283cf", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "c64e8b3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "05461fe", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "31bb911", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "e20fb1f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "f9becbf", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/metadata.yaml new file mode 100644 index 00000000..987d981e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/metadata.yaml @@ -0,0 +1,150 @@ +benchmark: E2 +family: Exposure Drift +drift_name: Image Bucket Policy Public +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "A bucket policy grants image read to a named partner account (C4, Mild), then to AnyPrincipal\ + \ (C7, Severe) \u2014 the images are exposed via bucket policy." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Image bucket policy Principal for s3:GetObject: named account -> ''*''.' +commit_count: 10 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 74283cf + label: V2 + message: 'feat: allow partner account to read published images' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: s3_bucket_policy_named + path: AWS 444455556666 -> s3:GetObject -> ImageStorageBucket/* + controls_present: + - SingleNamedAccount + controls_absent: [] + component: ImageStorageBucket + transitive_reach: + - one partner account reads images + construct_ids: + - ImageStorageBucket + template_assertion: BucketPolicy statement PartnerRead with AccountPrincipal 444455556666 + note: Cross-account read, scoped to one account. Mild. + C5: + sha: c64e8b3 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 05461fe + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C7: + sha: 31bb911 + label: V3 + message: 'fix: make gallery images readable by anyone' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_bucket_policy_public + path: anyone -> s3:GetObject -> ImageStorageBucket/* + controls_present: [] + controls_removed: + - SingleNamedAccount + component: ImageStorageBucket + transitive_reach: + - all user images, publicly + construct_ids: + - ImageStorageBucket + template_assertion: BucketPolicy Principal becomes '*' on s3:GetObject + note: Public read via bucket policy; orthogonal to the BlockPublicAccess axis (E1). + C8: + sha: e20fb1f + label: VB + message: 'docs: add data sharing runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C9: + sha: f9becbf + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C10: + sha: 82b4dd4 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- BlockPublicAccess on at C1 (policy still applies to principals) +- images are user-uploaded content at C1 +scoring: + drift_commits: + - C4 + - C7 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C8 + - C9 + - C10 + false_positive_probes: + - C5 + - C6 + - C10 + state_vs_event_probes: + - C8 + - C9 + severity_axis: image bucket-policy Principal breadth + note: Bucket-policy exposure, distinct from E1's BlockPublicAccess mechanism. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/repository new file mode 160000 index 00000000..82b4dd4e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/repository @@ -0,0 +1 @@ +Subproject commit 82b4dd4e2f926ad1fec07282b8d341f249a1dc79 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/template_analysis.json new file mode 100644 index 00000000..e5bf7378 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E2/template_analysis.json @@ -0,0 +1,296 @@ +{ + "benchmark": "E2", + "drift_name": "Image Bucket Policy Public", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "74283cf", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 33, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "c64e8b3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "05461fe", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "31bb911", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 13, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "e20fb1f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "f9becbf", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "82b4dd4", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/README.md new file mode 100644 index 00000000..6442867a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/README.md @@ -0,0 +1,145 @@ +# E3 — Image Bucket Encryption Weakened + +**Family:** Exposure Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The image bucket disables transit enforcement (C3, Mild), then at-rest encryption (C5, Severe) — user images stored and served without encryption guarantees. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Image bucket encryption: SSE + enforceSSL -> no TLS enforcement -> unencrypted. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `f13de2e` | V2 | **Mild** | Mild | 38 | **34** | +| C4 | `3e9733e` | VB | None | Mild | 38 | 254 | +| C5 | `a25123c` | V3 | **Severe** | Severe | 38 | **9** | +| C6 | `fab46d1` | VB | None | Severe | 38 | 2 | +| C7 | `a099da9` | VB | None | Severe | 38 | **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 254 lines**. +The smallest drift-bearing diff is **C5 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`fix: stop enforcing tls on image bucket for a legacy client` + +- **Mechanism:** `s3_enforcessl_off` +- **Path:** `plaintext client -> ImageStorageBucket` +- **Controls present:** AtRestEncryption +- **Controls removed:** EnforceSSL +- **Component:** `ImageStorageBucket` +- **Transitive reach:** objects transferable over plaintext +- **Template assertion:** bucket policy no longer denies non-TLS requests + +Transit protection dropped; at-rest still on. Mild. + +### C5 — V3, Severe + +`fix: disable at-rest encryption to simplify a cross-tool sync` + +- **Mechanism:** `s3_encryption_off` +- **Path:** `ImageStorageBucket objects at rest` +- **Controls present:** none +- **Controls removed:** AtRestEncryption, EnforceSSL +- **Component:** `ImageStorageBucket` +- **Transitive reach:** user images unencrypted at rest +- **Template assertion:** BucketEncryption removed (SSE-S3 -> none) + +No at-rest or transit encryption guarantee on the primary data store. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: extend image retention to two years` — retention only +- **C7** `docs: add storage runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- bucket holds user images at C1 +- no CMK/KMS option in the baseline + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4, C6, C7 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C6, C7 + +Encryption-weakening axis on the primary data store; two steps, transit then at-rest. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..c4a0faee --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/commit.json new file mode 100644 index 00000000..4f199b59 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "f13de2e", + "full_sha": "f13de2eb3ab0fc47b651c508259851ae002c7915", + "parent": "d4e7e9b", + "message": "fix: stop enforcing tls on image bucket for a legacy client", + "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/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/diff.patch new file mode 100644 index 00000000..779f2275 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..c4a0fae 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -107,7 +107,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, +- enforceSSL: props.enableTransitEncryption, ++ enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { + enabled: true, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..d15d58be --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/commit.json new file mode 100644 index 00000000..b9643ec9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "3e9733e", + "full_sha": "3e9733eeafe79d327c5d39256b48fc96920e84bb", + "parent": "f13de2e", + "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/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/diff.patch new file mode 100644 index 00000000..267bac05 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index c4a0fae..d15d58b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..29ac21f7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/commit.json new file mode 100644 index 00000000..2fe49e89 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "a25123c", + "full_sha": "a25123c182bb0846cb88f71eda21759abd4ca07a", + "parent": "3e9733e", + "message": "fix: disable at-rest encryption to simplify a cross-tool sync", + "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/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/diff.patch new file mode 100644 index 00000000..52227d97 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d15d58b..29ac21f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, +- encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, ++ encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..2874d898 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/commit.json new file mode 100644 index 00000000..7d3e7155 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "fab46d1", + "full_sha": "fab46d138ddf868ffdda472aa9be28659bc1dda2", + "parent": "a25123c", + "message": "chore: extend image retention to two years", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/diff.patch new file mode 100644 index 00000000..94a9b5e1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 29ac21f..2874d89 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -116,7 +116,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..2874d898 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + enforceSSL: false, // transit enforcement disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/commit.json new file mode 100644 index 00000000..a2103bce --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "a099da9", + "full_sha": "a099da965601c6d2d5672996569838ea8a4e8959", + "parent": "fab46d1", + "message": "docs: add storage 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/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/provenance.json new file mode 100644 index 00000000..8ee278b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/commits/provenance.json @@ -0,0 +1,107 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "f13de2e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "3e9733e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "a25123c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "fab46d1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "a099da9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/metadata.yaml new file mode 100644 index 00000000..e8dc098e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/metadata.yaml @@ -0,0 +1,123 @@ +benchmark: E3 +family: Exposure Drift +drift_name: Image Bucket Encryption Weakened +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The image bucket disables transit enforcement (C3, Mild), then at-rest encryption (C5,\ + \ Severe) \u2014 user images stored and served without encryption guarantees." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Image bucket encryption: SSE + enforceSSL -> no TLS enforcement -> unencrypted.' +commit_count: 7 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: f13de2e + label: V2 + message: 'fix: stop enforcing tls on image bucket for a legacy client' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_enforcessl_off + path: plaintext client -> ImageStorageBucket + controls_present: + - AtRestEncryption + controls_removed: + - EnforceSSL + component: ImageStorageBucket + transitive_reach: + - objects transferable over plaintext + construct_ids: + - ImageStorageBucket + template_assertion: bucket policy no longer denies non-TLS requests + note: Transit protection dropped; at-rest still on. Mild. + C4: + sha: 3e9733e + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: a25123c + label: V3 + message: 'fix: disable at-rest encryption to simplify a cross-tool sync' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_encryption_off + path: ImageStorageBucket objects at rest + controls_present: [] + controls_removed: + - AtRestEncryption + - EnforceSSL + component: ImageStorageBucket + transitive_reach: + - user images unencrypted at rest + construct_ids: + - ImageStorageBucket + template_assertion: BucketEncryption removed (SSE-S3 -> none) + note: No at-rest or transit encryption guarantee on the primary data store. + C6: + sha: fab46d1 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: retention only + C7: + sha: a099da9 + label: VB + message: 'docs: add storage runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- bucket holds user images at C1 +- no CMK/KMS option in the baseline +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + - C6 + - C7 + false_positive_probes: + - C4 + state_vs_event_probes: + - C6 + - C7 + severity_axis: image bucket encryption strength (transit then at-rest) + note: Encryption-weakening axis on the primary data store; two steps, transit then at-rest. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/repository new file mode 160000 index 00000000..a099da96 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/repository @@ -0,0 +1 @@ +Subproject commit a099da965601c6d2d5672996569838ea8a4e8959 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/template_analysis.json new file mode 100644 index 00000000..70071c87 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E3/template_analysis.json @@ -0,0 +1,209 @@ +{ + "benchmark": "E3", + "drift_name": "Image Bucket Encryption Weakened", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "f13de2e", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 34, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "3e9733e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "a25123c", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 9, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "fab46d1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "a099da9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/README.md new file mode 100644 index 00000000..c1f2e03f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/README.md @@ -0,0 +1,162 @@ +# E4 — Image Bucket CORS Any-Origin + +**Family:** Exposure Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The image bucket adds a CORS rule scoped to the app origin (C6, Mild), then opens allowedOrigins to '*' (C10, Severe) — any site can drive browser uploads/downloads. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +S3 CORS allowedOrigins: none -> app origin -> '*'. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `43cabbb` | VB | None | None | 38 | 2 | +| C6 | `92fda32` | V2 | **Mild** | Mild | 38 | **16** | +| C7 | `8904af3` | VB | None | Mild | 38 | 254 | +| C8 | `cf962d9` | VB | None | Mild | 42 | 139 | +| C9 | `280823e` | VB | None | Mild | 42 | 2 | +| C10 | `6aaebe4` | V3 | **Severe** | Severe | 42 | **2** | +| C11 | `fe8da93` | VB | None | Severe | 42 | 5 | +| C12 | `b08a35d` | VB | None | Severe | 42 | **0** | +| C13 | `f57d497` | VB | None | Severe | 42 | **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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: add cors for browser uploads from the app origin` + +- **Mechanism:** `s3_cors_named_origin` +- **Path:** `https://app.example.com (browser) -> ImageStorageBucket` +- **Controls present:** OriginAllowlist +- **Component:** `ImageStorageBucket` +- **Transitive reach:** browser uploads from the app origin +- **Template assertion:** CorsConfiguration AllowedOrigins = the app origin + +CORS scoped to one origin. Mild. + +### C10 — V3, Severe + +`fix: allow cors from any origin for embed partners` + +- **Mechanism:** `s3_cors_any_origin` +- **Path:** `any website (browser) -> ImageStorageBucket` +- **Controls present:** none +- **Controls removed:** OriginAllowlist +- **Component:** `ImageStorageBucket` +- **Transitive reach:** cross-origin uploads/downloads from any site +- **Template assertion:** CorsConfiguration AllowedOrigins becomes ['*'] + +Any origin can drive authenticated browser S3 requests; image-upload-specific exposure. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C5** `chore: widen cloudfront price class` — edge coverage only +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: enable vpc flow logs` — security improvement decoy +- **C9** `chore: grow root volume to 50gb` — block device size only +- **C11** `chore: add vpc id stack output` — cosmetic output +- **C12** `docs: add upload runbook` — no template change while cumulative Severe +- **C13** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- presigned uploads rely on origin trust at C1 +- no CORS restriction beyond app logic at C1 + +## Scoring + +- **Drift commits:** C6, C10 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C11, C12, C13 +- **False-positive probes:** C7, C8 + +- **State-vs-event probes:** C11, C12, C13 + +Image-upload-specific exposure axis; no analog in the prior four suites. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..cad64eb8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['*'], // any origin + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/commit.json new file mode 100644 index 00000000..28976767 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "6aaebe4", + "full_sha": "6aaebe4ddc065e7a1e64c18acb7fda332280b95e", + "parent": "280823e", + "message": "fix: allow cors from any origin for embed partners", + "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/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/diff.patch new file mode 100644 index 00000000..5d218f12 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index ffba0bf..cad64eb 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -136,7 +136,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + ); + + this.imageBucket.addCorsRule({ +- allowedOrigins: ['https://app.example.com'], ++ allowedOrigins: ['*'], // any origin + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..dcbedf8e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/cdk.ts @@ -0,0 +1,344 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['*'], // any origin + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/commit.json new file mode 100644 index 00000000..41032ac6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "fe8da93", + "full_sha": "fe8da9306ea504bdcc773178365bc859f7d6f79e", + "parent": "6aaebe4", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/diff.patch new file mode 100644 index 00000000..a63eb9cf --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index cad64eb..dcbedf8 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -274,6 +274,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..dcbedf8e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/cdk.ts @@ -0,0 +1,344 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['*'], // any origin + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/commit.json new file mode 100644 index 00000000..6e5dee38 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "b08a35d", + "full_sha": "b08a35d15b218327fa20f15bc005e724076d7996", + "parent": "fe8da93", + "message": "docs: add upload 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/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..dcbedf8e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/cdk.ts @@ -0,0 +1,344 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['*'], // any origin + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/commit.json new file mode 100644 index 00000000..b565a892 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "f57d497", + "full_sha": "f57d49733b2cc562f2d694f24cf4a6139d02e511", + "parent": "b08a35d", + "message": "chore: extend gitignore", + "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": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C13/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..52a31153 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/commit.json new file mode 100644 index 00000000..e85d8e9a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "43cabbb", + "full_sha": "43cabbb760ea90783e1d82b748fa13c3d6350e33", + "parent": "8101537", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/diff.patch new file mode 100644 index 00000000..51632666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..52a3115 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -256,7 +256,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..e20834c7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/cdk.ts @@ -0,0 +1,336 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['https://app.example.com'], + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/commit.json new file mode 100644 index 00000000..6f2a1e53 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "92fda32", + "full_sha": "92fda3227c75bf995bb52e7b02722bb90a59f266", + "parent": "43cabbb", + "message": "feat: add cors for browser uploads from the app origin", + "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 | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/diff.patch new file mode 100644 index 00000000..f62ed548 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C6/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 52a3115..e20834c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -128,6 +128,12 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }) + ); + ++ this.imageBucket.addCorsRule({ ++ allowedOrigins: ['https://app.example.com'], ++ allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], ++ allowedHeaders: ['*'], ++ }); ++ + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..69635191 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/cdk.ts @@ -0,0 +1,341 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['https://app.example.com'], + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/commit.json new file mode 100644 index 00000000..edba5e81 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "8904af3", + "full_sha": "8904af3a81f1ef034bfe083060d7f0bbc2770593", + "parent": "92fda32", + "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/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/diff.patch new file mode 100644 index 00000000..0171f9f7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index e20834c..6963519 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..5115f6db --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['https://app.example.com'], + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/commit.json new file mode 100644 index 00000000..11b5ae79 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "cf962d9", + "full_sha": "cf962d935469092b5a6c81e7e1ad73aff7294217", + "parent": "8904af3", + "message": "chore: enable vpc flow logs", + "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 | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/diff.patch new file mode 100644 index 00000000..2dde17dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 6963519..5115f6d 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..ffba0bf9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addCorsRule({ + allowedOrigins: ['https://app.example.com'], + allowedMethods: [s3.HttpMethods.GET, s3.HttpMethods.PUT], + allowedHeaders: ['*'], + }); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/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/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/commit.json new file mode 100644 index 00000000..8a81ee68 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "280823e", + "full_sha": "280823e96159b67d13178e0544abf6f04809a815", + "parent": "cf962d9", + "message": "chore: grow root volume to 50gb", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/diff.patch new file mode 100644 index 00000000..d05dacd6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 5115f6d..ffba0bf 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -162,7 +162,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/provenance.json new file mode 100644 index 00000000..38d505e5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/commits/provenance.json @@ -0,0 +1,197 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "6aaebe4", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "fe8da93", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "b08a35d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "f57d497", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "43cabbb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "92fda32", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8904af3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "cf962d9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "280823e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/metadata.yaml new file mode 100644 index 00000000..553e6eb6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/metadata.yaml @@ -0,0 +1,177 @@ +benchmark: E4 +family: Exposure Drift +drift_name: Image Bucket CORS Any-Origin +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The image bucket adds a CORS rule scoped to the app origin (C6, Mild), then opens allowedOrigins\ + \ to '*' (C10, Severe) \u2014 any site can drive browser uploads/downloads." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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 CORS allowedOrigins: none -> app origin -> ''*''.' +commit_count: 13 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: 43cabbb + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: edge coverage only + C6: + sha: 92fda32 + label: V2 + message: 'feat: add cors for browser uploads from the app origin' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: s3_cors_named_origin + path: https://app.example.com (browser) -> ImageStorageBucket + controls_present: + - OriginAllowlist + controls_absent: [] + component: ImageStorageBucket + transitive_reach: + - browser uploads from the app origin + construct_ids: + - ImageStorageBucket + template_assertion: CorsConfiguration AllowedOrigins = the app origin + note: CORS scoped to one origin. Mild. + C7: + sha: 8904af3 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: cf962d9 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C9: + sha: 280823e + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C10: + sha: 6aaebe4 + label: V3 + message: 'fix: allow cors from any origin for embed partners' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_cors_any_origin + path: any website (browser) -> ImageStorageBucket + controls_present: [] + controls_removed: + - OriginAllowlist + component: ImageStorageBucket + transitive_reach: + - cross-origin uploads/downloads from any site + construct_ids: + - ImageStorageBucket + template_assertion: CorsConfiguration AllowedOrigins becomes ['*'] + note: Any origin can drive authenticated browser S3 requests; image-upload-specific exposure. + C11: + sha: fe8da93 + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C12: + sha: b08a35d + label: VB + message: 'docs: add upload runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C13: + sha: f57d497 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- presigned uploads rely on origin trust at C1 +- no CORS restriction beyond app logic at C1 +scoring: + drift_commits: + - C6 + - C10 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C11 + - C12 + - C13 + false_positive_probes: + - C7 + - C8 + state_vs_event_probes: + - C11 + - C12 + - C13 + severity_axis: S3 CORS allowedOrigins breadth + note: Image-upload-specific exposure axis; no analog in the prior four suites. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/repository new file mode 160000 index 00000000..f57d4973 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/repository @@ -0,0 +1 @@ +Subproject commit f57d49733b2cc562f2d694f24cf4a6139d02e511 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/template_analysis.json new file mode 100644 index 00000000..1950fb8a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E4/template_analysis.json @@ -0,0 +1,383 @@ +{ + "benchmark": "E4", + "drift_name": "Image Bucket CORS Any-Origin", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "43cabbb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "92fda32", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 16, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "8904af3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "cf962d9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "280823e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "6aaebe4", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "fe8da93", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "b08a35d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C13": { + "sha": "f57d497", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/README.md new file mode 100644 index 00000000..59b709f1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/README.md @@ -0,0 +1,141 @@ +# E5 — EBS Encryption Off + +**Family:** Exposure Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +An unencrypted scratch EBS volume is added (C4, Mild), then the root volume's encryption is disabled (C5, Severe) — the instance's disks lose at-rest encryption. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +EBS at-rest encryption: root encrypted -> unencrypted scratch added -> root unencrypted. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `f69447c` | VB | None | None | 38 | 254 | +| C4 | `679c705` | V2 | **Mild** | Mild | 38 | **8** | +| C5 | `e838a2d` | V3 | **Severe** | Severe | 38 | **2** | +| C6 | `13befc3` | VB | None | Severe | 38 | **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 **C3 at 254 lines**. +The smallest drift-bearing diff is **C5 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: add scratch data volume for image processing temp files` + +- **Mechanism:** `ebs_scratch_unencrypted` +- **Path:** `scratch volume at rest` +- **Controls present:** RootEncrypted +- **Component:** `WebServerInstance` +- **Transitive reach:** transient data unencrypted +- **Template assertion:** BlockDeviceMappings gains /dev/xvdb with Encrypted false + +A new unencrypted volume, but only scratch data. Mild. + +### C5 — V3, Severe + +`fix: disable root ebs encryption to reuse a prebuilt ami snapshot` + +- **Mechanism:** `ebs_root_unencrypted` +- **Path:** `root volume at rest` +- **Controls present:** none +- **Controls removed:** RootEncrypted +- **Component:** `WebServerInstance` +- **Transitive reach:** OS and app disk unencrypted +- **Template assertion:** root device Ebs.Encrypted true -> false + +Primary instance storage unencrypted; snapshots inherit the exposure. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: apply cost-allocation tags` — largest template diff +- **C6** `docs: add storage runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- EBS encryption is flag-gated at C1 +- instance may cache images/tokens on disk at C1 + +## Scoring + +- **Drift commits:** C4, C5 +- **Benign commits:** C2, C3, C6 +- **False-positive probes:** C3 + +- **State-vs-event probes:** C6 + +At-rest encryption on compute storage; distinct asset from E3's S3 encryption. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..987e5900 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/commit.json new file mode 100644 index 00000000..c0aecbcc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "f69447c", + "full_sha": "f69447c37dd1011c755686eeafc0cc4a2e74faf3", + "parent": "d4e7e9b", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/diff.patch new file mode 100644 index 00000000..11bbcad4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C3/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..987e590 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..8ce181a1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + { + deviceName: '/dev/xvdb', // scratch data volume + volume: ec2.BlockDeviceVolume.ebs(20, { + encrypted: false, // scratch volume unencrypted + volumeType: ec2.EbsDeviceVolumeType.GP3, + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/commit.json new file mode 100644 index 00000000..308941b8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "679c705", + "full_sha": "679c7054c4f8ec9c403e5b94de9d24e528880dcb", + "parent": "f69447c", + "message": "feat: add scratch data volume for image processing temp files", + "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 | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/diff.patch new file mode 100644 index 00000000..eac9f901 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C4/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 987e590..8ce181a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,6 +160,13 @@ export class SimpleImageWebAppStack extends cdk.Stack { + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, ++ { ++ deviceName: '/dev/xvdb', // scratch data volume ++ volume: ec2.BlockDeviceVolume.ebs(20, { ++ encrypted: false, // scratch volume unencrypted ++ volumeType: ec2.EbsDeviceVolumeType.GP3, ++ }), ++ }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..1dfab37c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: false, // root volume unencrypted + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + { + deviceName: '/dev/xvdb', // scratch data volume + volume: ec2.BlockDeviceVolume.ebs(20, { + encrypted: false, // scratch volume unencrypted + volumeType: ec2.EbsDeviceVolumeType.GP3, + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/commit.json new file mode 100644 index 00000000..78dea856 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "e838a2d", + "full_sha": "e838a2d2d9d0dbafd85150b5484f7716a1df5a7f", + "parent": "679c705", + "message": "fix: disable root ebs encryption to reuse a prebuilt ami snapshot", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "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/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/diff.patch new file mode 100644 index 00000000..f0142497 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 8ce181a..1dfab37 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -155,7 +155,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume +- encrypted: props.enableAtRestEncryptionEBS, ++ encrypted: false, // root volume unencrypted + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..1dfab37c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: false, // root volume unencrypted + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + { + deviceName: '/dev/xvdb', // scratch data volume + volume: ec2.BlockDeviceVolume.ebs(20, { + encrypted: false, // scratch volume unencrypted + volumeType: ec2.EbsDeviceVolumeType.GP3, + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/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/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/commit.json new file mode 100644 index 00000000..115fe93c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "13befc3", + "full_sha": "13befc3bc0472c6db82244da46180dcd9b218fd2", + "parent": "e838a2d", + "message": "docs: add storage 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/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/provenance.json new file mode 100644 index 00000000..c0d930ff --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "f69447c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "679c705", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "e838a2d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "13befc3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/metadata.yaml new file mode 100644 index 00000000..04982348 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/metadata.yaml @@ -0,0 +1,112 @@ +benchmark: E5 +family: Exposure Drift +drift_name: EBS Encryption Off +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "An unencrypted scratch EBS volume is added (C4, Mild), then the root volume's encryption\ + \ is disabled (C5, Severe) \u2014 the instance's disks lose at-rest encryption." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'EBS at-rest encryption: root encrypted -> unencrypted scratch added -> root unencrypted.' +commit_count: 6 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: f69447c + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: largest template diff + C4: + sha: 679c705 + label: V2 + message: 'feat: add scratch data volume for image processing temp files' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: ebs_scratch_unencrypted + path: scratch volume at rest + controls_present: + - RootEncrypted + controls_absent: + - ScratchEncrypted + component: WebServerInstance + transitive_reach: + - transient data unencrypted + construct_ids: + - WebServerInstance + template_assertion: BlockDeviceMappings gains /dev/xvdb with Encrypted false + note: A new unencrypted volume, but only scratch data. Mild. + C5: + sha: e838a2d + label: V3 + message: 'fix: disable root ebs encryption to reuse a prebuilt ami snapshot' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: ebs_root_unencrypted + path: root volume at rest + controls_present: [] + controls_removed: + - RootEncrypted + component: WebServerInstance + transitive_reach: + - OS and app disk unencrypted + construct_ids: + - WebServerInstance + template_assertion: root device Ebs.Encrypted true -> false + note: Primary instance storage unencrypted; snapshots inherit the exposure. + C6: + sha: 13befc3 + label: VB + message: 'docs: add storage runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- EBS encryption is flag-gated at C1 +- instance may cache images/tokens on disk at C1 +scoring: + drift_commits: + - C4 + - C5 + benign_commits: + - C2 + - C3 + - C6 + false_positive_probes: + - C3 + state_vs_event_probes: + - C6 + severity_axis: EBS at-rest encryption (scratch then root) + note: At-rest encryption on compute storage; distinct asset from E3's S3 encryption. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/repository new file mode 160000 index 00000000..13befc3b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/repository @@ -0,0 +1 @@ +Subproject commit 13befc3bc0472c6db82244da46180dcd9b218fd2 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/template_analysis.json new file mode 100644 index 00000000..15e5321c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E5/template_analysis.json @@ -0,0 +1,180 @@ +{ + "benchmark": "E5", + "drift_name": "EBS Encryption Off", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "f69447c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "679c705", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 8, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "e838a2d", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "13befc3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/README.md new file mode 100644 index 00000000..a6d525bc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/README.md @@ -0,0 +1,166 @@ +# E6 — CloudFront Viewer Plaintext HTTP + +**Family:** Exposure Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +CloudFront lowers its minimum TLS version (C7, Mild), then allows plaintext HTTP to viewers (C11, Severe) — image traffic can flow unencrypted end to viewer. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +CloudFront viewer transit: TLS1.2+redirect -> weak TLS -> allow HTTP. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `43cabbb` | VB | None | None | 38 | 2 | +| C6 | `ded0767` | VB | None | None | 38 | 2 | +| C7 | `e4ba2bf` | V2 | **Mild** | Mild | 38 | **0** | +| C8 | `e771204` | VB | None | Mild | 38 | 254 | +| C9 | `c9cdc94` | VB | None | Mild | 42 | 139 | +| C10 | `2b705f3` | VB | None | Mild | 42 | 5 | +| C11 | `7434889` | V3 | **Severe** | Severe | 42 | **2** | +| C12 | `c64cf64` | VB | None | Severe | 42 | **0** | +| C13 | `b810f50` | VB | None | Severe | 42 | **0** | +| C14 | `36ac68c` | VB | None | Severe | 42 | **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 **C8 at 254 lines**. +The smallest drift-bearing diff is **C7 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C14 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C7 — V2, Mild + +`fix: lower cloudfront min tls for an old mobile client` + +- **Mechanism:** `cloudfront_weak_tls` +- **Path:** `viewer -> (TLS1) -> CloudFront` +- **Controls present:** HttpsRedirect +- **Controls removed:** StrongTls +- **Component:** `AppCdn` +- **Transitive reach:** downgraded TLS negotiation +- **Template assertion:** ViewerCertificate MinimumProtocolVersion TLSv1.2_2021 -> TLSv1 + +Weaker TLS but still HTTPS. Mild. + +### C11 — V3, Severe + +`fix: allow http on cloudfront to debug a redirect loop` + +- **Mechanism:** `cloudfront_allow_http` +- **Path:** `viewer -> plaintext HTTP -> CloudFront` +- **Controls present:** none +- **Controls removed:** HttpsRedirect, StrongTls +- **Component:** `AppCdn` +- **Transitive reach:** image traffic served over plaintext HTTP +- **Template assertion:** DefaultCacheBehavior ViewerProtocolPolicy redirect-to-https -> allow-all + +End-to-viewer transit protection removed. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C5** `chore: widen cloudfront price class` — edge coverage only +- **C6** `chore: grow root volume to 50gb` — block device size only +- **C8** `chore: apply cost-allocation tags` — largest template diff +- **C9** `chore: enable vpc flow logs` — security improvement decoy +- **C10** `chore: add vpc id stack output` — cosmetic output +- **C12** `docs: add cdn runbook` — no template change while cumulative Severe +- **C13** `chore: extend gitignore` — zero template diff +- **C14** `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. + +- CloudFront-to-ALB origin is HTTP_ONLY at C1 +- no WAF on the distribution at C1 + +## Scoring + +- **Drift commits:** C7, C11 +- **Benign commits:** C2, C3, C4, C5, C6, C8, C9, C10, C12, C13, C14 +- **False-positive probes:** C8, C9 + +- **State-vs-event probes:** C12, C13, C14 + +Edge transit-weakening; two steps (weak TLS, then plaintext HTTP). + +## Layout + +``` +E6/ +├── 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 e6 --baseline /text-paste-service/v1/cdk --out E6 +python3 generators/synth.py --root E6 --workbench /tmp/wb +python3 generators/analyze.py --root E6 +python3 generators/verify.py --benchmark e6 --root E6 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/e6.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/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..089ed40e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/commit.json new file mode 100644 index 00000000..340d619a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "2b705f3", + "full_sha": "2b705f311e237d65c8af2ad362eb26ee3e7c1b7c", + "parent": "c9cdc94", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/diff.patch new file mode 100644 index 00000000..b5732a4f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 266ec8f..089ed40 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -268,6 +268,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..cbf87006 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL, // plaintext HTTP allowed + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/commit.json new file mode 100644 index 00000000..2d196a1b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "7434889", + "full_sha": "7434889fe0899d834e923fcbc587e4c4c3a077fd", + "parent": "2b705f3", + "message": "fix: allow http on cloudfront to debug a redirect loop", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10: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/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/diff.patch new file mode 100644 index 00000000..9a7c7a4e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 089ed40..cbf8700 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -245,7 +245,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), +- viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, ++ viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL, // plaintext HTTP allowed + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..cbf87006 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL, // plaintext HTTP allowed + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/commit.json new file mode 100644 index 00000000..2bffb58e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "c64cf64", + "full_sha": "c64cf64b68aa9829a15c4309d3a4c315d1aa2ad5", + "parent": "7434889", + "message": "docs: add cdn 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/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..cbf87006 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL, // plaintext HTTP allowed + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/commit.json new file mode 100644 index 00000000..9ba15c0b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "b810f50", + "full_sha": "b810f500e8e5f08de6290721dbacf1e5cd74383a", + "parent": "c64cf64", + "message": "chore: extend gitignore", + "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": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C13/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.ts new file mode 100644 index 00000000..cbf87006 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.ALLOW_ALL, // plaintext HTTP allowed + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/commit.json new file mode 100644 index 00000000..022373f9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C14", + "sha": "36ac68c", + "full_sha": "36ac68cf6081a2e678c4459b5b6767a44ba88e7d", + "parent": "b810f50", + "message": "docs: on-call contact note", + "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/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/diff.patch new file mode 100644 index 00000000..41bde40e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C14/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 3e4ecaf..a3b057f 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ read/write for presigned upload/download URLs. + + - Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. + - CloudFront fronts the ALB; the web server sits in a private subnet. ++ ++## Contacts ++ ++- On-call: web-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..52a31153 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/commit.json new file mode 100644 index 00000000..e85d8e9a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "43cabbb", + "full_sha": "43cabbb760ea90783e1d82b748fa13c3d6350e33", + "parent": "8101537", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/diff.patch new file mode 100644 index 00000000..51632666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..52a3115 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -256,7 +256,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..22a06bfd --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/commit.json new file mode 100644 index 00000000..00f92113 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "ded0767", + "full_sha": "ded0767110c388f3caac270a983a232466267b9d", + "parent": "43cabbb", + "message": "chore: grow root volume to 50gb", + "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": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/diff.patch new file mode 100644 index 00000000..7064913d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 52a3115..22a06bf 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -149,7 +149,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..df0a161f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/commit.json new file mode 100644 index 00000000..9bb03916 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "e4ba2bf", + "full_sha": "e4ba2bf6e49f579089801d10d195628709c94beb", + "parent": "ded0767", + "message": "fix: lower cloudfront min tls for an old mobile client", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10: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/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/diff.patch new file mode 100644 index 00000000..12d9d038 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 22a06bf..df0a161 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -257,7 +257,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage +- minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, ++ minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..0224918a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/commit.json new file mode 100644 index 00000000..2dca6e89 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "e771204", + "full_sha": "e77120445261080c3ad10b01e86fbeae67c799e5", + "parent": "e4ba2bf", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-22T10: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/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/diff.patch new file mode 100644 index 00000000..f72e95b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C8/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index df0a161..0224918 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..266ec8ff --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1, // weakened + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/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/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/commit.json new file mode 100644 index 00000000..cb264eac --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "c9cdc94", + "full_sha": "c9cdc94097b72849f30298bdf073514eaae2385c", + "parent": "e771204", + "message": "chore: enable vpc flow logs", + "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 | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/diff.patch new file mode 100644 index 00000000..ff9f98ae --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 0224918..266ec8f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/provenance.json new file mode 100644 index 00000000..a88ff7ff --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/commits/provenance.json @@ -0,0 +1,212 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "2b705f3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "7434889", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "c64cf64", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "b810f50", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C14": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "36ac68c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "43cabbb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "ded0767", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "e4ba2bf", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "e771204", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "c9cdc94", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/metadata.yaml new file mode 100644 index 00000000..00cd18a7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/metadata.yaml @@ -0,0 +1,188 @@ +benchmark: E6 +family: Exposure Drift +drift_name: CloudFront Viewer Plaintext HTTP +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "CloudFront lowers its minimum TLS version (C7, Mild), then allows plaintext HTTP to\ + \ viewers (C11, Severe) \u2014 image traffic can flow unencrypted end to viewer." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'CloudFront viewer transit: TLS1.2+redirect -> weak TLS -> allow HTTP.' +commit_count: 14 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: 43cabbb + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: edge coverage only + C6: + sha: ded0767 + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: block device size only + C7: + sha: e4ba2bf + label: V2 + message: 'fix: lower cloudfront min tls for an old mobile client' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: cloudfront_weak_tls + path: viewer -> (TLS1) -> CloudFront + controls_present: + - HttpsRedirect + controls_removed: + - StrongTls + component: AppCdn + transitive_reach: + - downgraded TLS negotiation + construct_ids: + - AppCdn + template_assertion: ViewerCertificate MinimumProtocolVersion TLSv1.2_2021 -> TLSv1 + note: Weaker TLS but still HTTPS. Mild. + C8: + sha: e771204 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C9: + sha: c9cdc94 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C10: + sha: 2b705f3 + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C11: + sha: '7434889' + label: V3 + message: 'fix: allow http on cloudfront to debug a redirect loop' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: cloudfront_allow_http + path: viewer -> plaintext HTTP -> CloudFront + controls_present: [] + controls_removed: + - HttpsRedirect + - StrongTls + component: AppCdn + transitive_reach: + - image traffic served over plaintext HTTP + construct_ids: + - AppCdn + template_assertion: DefaultCacheBehavior ViewerProtocolPolicy redirect-to-https -> allow-all + note: End-to-viewer transit protection removed. + C12: + sha: c64cf64 + label: VB + message: 'docs: add cdn runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C13: + sha: b810f50 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C14: + sha: 36ac68c + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- CloudFront-to-ALB origin is HTTP_ONLY at C1 +- no WAF on the distribution at C1 +scoring: + drift_commits: + - C7 + - C11 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C6 + - C8 + - C9 + - C10 + - C12 + - C13 + - C14 + false_positive_probes: + - C8 + - C9 + state_vs_event_probes: + - C12 + - C13 + - C14 + severity_axis: CloudFront viewer transit strength (TLS version, then HTTP allowed) + note: Edge transit-weakening; two steps (weak TLS, then plaintext HTTP). diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/repository new file mode 160000 index 00000000..36ac68cf --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/repository @@ -0,0 +1 @@ +Subproject commit 36ac68cf6081a2e678c4459b5b6767a44ba88e7d diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/template_analysis.json new file mode 100644 index 00000000..6d5ebfc5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/E6/template_analysis.json @@ -0,0 +1,412 @@ +{ + "benchmark": "E6", + "drift_name": "CloudFront Viewer Plaintext HTTP", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "43cabbb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "ded0767", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "e4ba2bf", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "e771204", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "c9cdc94", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "2b705f3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 42, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "7434889", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "c64cf64", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C13": { + "sha": "b810f50", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C14": { + "sha": "36ac68c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/README.md new file mode 100644 index 00000000..dec66031 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/README.md @@ -0,0 +1,147 @@ +# G1 — Web Server Role S3 Wildcard + +**Family:** Privilege Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The web server role's S3 statement broadens to s3:* on the image bucket (C4, Mild), then to resources: ['*'] (C6, Severe) — read/write/delete on every bucket. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Role S3 grant: Get/Put/List on the bucket -> s3:* -> s3:* 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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `9aa8c09` | V2 | **Mild** | Mild | 38 | **6** | +| C5 | `5247398` | VB | None | Mild | 38 | 254 | +| C6 | `eba4f9b` | V3 | **Severe** | Severe | 38 | **23** | +| C7 | `b904e8f` | VB | None | Severe | 42 | 139 | +| C8 | `e72f311` | VB | None | Severe | 42 | **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 254 lines**. +The smallest drift-bearing diff is **C4 at 6 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: broaden s3 actions for the media processing pipeline` + +- **Mechanism:** `s3_action_wildcard` +- **Path:** `WebServerRole -> s3:* -> ImageStorageBucket` +- **Controls present:** BucketScoped +- **Component:** `WebServerRole` +- **Transitive reach:** all S3 actions on the image bucket +- **Template assertion:** policy S3 actions collapse to s3:*, resource still the bucket ARN + +Action wildcard, resource-scoped. Mild. + +### C6 — V3, Severe + +`fix: widen s3 resource for cross-bucket media imports` + +- **Mechanism:** `s3_resource_wildcard` +- **Path:** `WebServerRole -> s3:* -> * (any bucket)` +- **Controls present:** none +- **Controls removed:** BucketScoped +- **Component:** `WebServerRole` +- **Transitive reach:** every bucket in the account +- **Template assertion:** S3 statement Resource becomes '*' + +An instance-role compromise now reaches all account buckets. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: enable vpc flow logs` — security improvement decoy +- **C8** `docs: add iam runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- grantReadWrite already covers the image bucket at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7, C8 +- **False-positive probes:** C5, C7 + +- **State-vs-event probes:** C8 + +Action wildcard at Mild, resource wildcard at Severe; the instance role is SSRF-reachable. + +## 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 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/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..163ed7ca --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:*'], // broadened for the media pipeline + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/commit.json new file mode 100644 index 00000000..74aafa9a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "9aa8c09", + "full_sha": "9aa8c0936d847207492c21f61922cbc55ae2f681", + "parent": "10163b2", + "message": "feat: broaden s3 actions for the media processing pipeline", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/diff.patch new file mode 100644 index 00000000..79905fc8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..163ed7c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -123,7 +123,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ +- actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic ++ actions: ['s3:*'], // broadened for the media pipeline + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..fa21801b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:*'], // broadened for the media pipeline + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/commit.json new file mode 100644 index 00000000..d902f335 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "5247398", + "full_sha": "524739862b13457948373ed2637a6d3d584ab85f", + "parent": "9aa8c09", + "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/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/diff.patch new file mode 100644 index 00000000..79481503 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 163ed7c..fa21801 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..2e787de9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:*'], // broadened for the media pipeline + resources: ['*'], // any bucket + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/commit.json new file mode 100644 index 00000000..71143616 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "eba4f9b", + "full_sha": "eba4f9bb9ca4a4fdba3392fed1f923a862db3efc", + "parent": "5247398", + "message": "fix: widen s3 resource for cross-bucket media imports", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/diff.patch new file mode 100644 index 00000000..bba9dfaa --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C6/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index fa21801..2e787de 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -129,7 +129,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:*'], // broadened for the media pipeline +- resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], ++ resources: ['*'], // any bucket + }) + ); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..5a76d480 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:*'], // broadened for the media pipeline + resources: ['*'], // any bucket + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/commit.json new file mode 100644 index 00000000..ca934a43 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "b904e8f", + "full_sha": "b904e8fcd2631bbf7c939ab8eaff59b57cb94855", + "parent": "eba4f9b", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/diff.patch new file mode 100644 index 00000000..0eba2bc5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e787de..5a76d48 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..5a76d480 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:*'], // broadened for the media pipeline + resources: ['*'], // any bucket + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/commit.json new file mode 100644 index 00000000..67f82ced --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "e72f311", + "full_sha": "e72f31112fe9aa334ceb152ae3754925fd921212", + "parent": "b904e8f", + "message": "docs: add iam 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/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/provenance.json new file mode 100644 index 00000000..4719159e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/commits/provenance.json @@ -0,0 +1,122 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "9aa8c09", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "5247398", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "eba4f9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "b904e8f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "e72f311", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/metadata.yaml new file mode 100644 index 00000000..5dd72bb5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/metadata.yaml @@ -0,0 +1,130 @@ +benchmark: G1 +family: Privilege Drift +drift_name: Web Server Role S3 Wildcard +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The web server role's S3 statement broadens to s3:* on the image bucket (C4, Mild),\ + \ then to resources: ['*'] (C6, Severe) \u2014 read/write/delete on every bucket." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Role S3 grant: Get/Put/List on the bucket -> s3:* -> s3:* on ''*''.' +commit_count: 8 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 9aa8c09 + label: V2 + message: 'feat: broaden s3 actions for the media processing pipeline' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: s3_action_wildcard + path: WebServerRole -> s3:* -> ImageStorageBucket + controls_present: + - BucketScoped + controls_absent: [] + component: WebServerRole + transitive_reach: + - all S3 actions on the image bucket + construct_ids: + - WebServerRole + template_assertion: policy S3 actions collapse to s3:*, resource still the bucket ARN + note: Action wildcard, resource-scoped. Mild. + C5: + sha: '5247398' + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: eba4f9b + label: V3 + message: 'fix: widen s3 resource for cross-bucket media imports' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: s3_resource_wildcard + path: WebServerRole -> s3:* -> * (any bucket) + controls_present: [] + controls_removed: + - BucketScoped + component: WebServerRole + transitive_reach: + - every bucket in the account + construct_ids: + - WebServerRole + template_assertion: S3 statement Resource becomes '*' + note: An instance-role compromise now reaches all account buckets. + C7: + sha: b904e8f + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C8: + sha: e72f311 + label: VB + message: 'docs: add iam runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- grantReadWrite already covers the image bucket at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 +scoring: + drift_commits: + - C4 + - C6 + benign_commits: + - C2 + - C3 + - C5 + - C7 + - C8 + false_positive_probes: + - C5 + - C7 + state_vs_event_probes: + - C8 + severity_axis: web server role S3 grant breadth + note: Action wildcard at Mild, resource wildcard at Severe; the instance role is SSRF-reachable. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/repository new file mode 160000 index 00000000..e72f3111 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/repository @@ -0,0 +1 @@ +Subproject commit e72f31112fe9aa334ceb152ae3754925fd921212 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/template_analysis.json new file mode 100644 index 00000000..e8a7b863 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G1/template_analysis.json @@ -0,0 +1,254 @@ +{ + "benchmark": "G1", + "drift_name": "Web Server Role S3 Wildcard", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "9aa8c09", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 6, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "5247398", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "eba4f9b", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 23, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "b904e8f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "e72f311", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/README.md new file mode 100644 index 00000000..3f6125d0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/README.md @@ -0,0 +1,138 @@ +# G2 — SSM Session to Full SSM + +**Family:** Privilege Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The web server role gains ssm:StartSession on a named document (C2, Mild), then full ssm:* on '*' (C4, Severe) — command execution on any instance. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +SSM grant: StartSession on a doc -> ssm:* 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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `7acaaf8` | V2 | **Mild** | Mild | 38 | **5** | +| C3 | `3c3c4ef` | VB | None | Mild | 38 | 254 | +| C4 | `e97003b` | V3 | **Severe** | Severe | 38 | **4** | +| C5 | `d6767c0` | VB | None | Severe | 38 | 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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: allow session manager shell on the web server` + +- **Mechanism:** `ssm_start_session_scoped` +- **Path:** `WebServerRole -> ssm:StartSession -> run-shell document` +- **Controls present:** ScopedDocument +- **Component:** `WebServerRole` +- **Transitive reach:** interactive shell on this instance +- **Template assertion:** policy ssm:StartSession Resource = a named SSM document ARN + +Session access, document-scoped. Mild. + +### C4 — V3, Severe + +`fix: grant full ssm to unblock fleet automation` + +- **Mechanism:** `ssm_full` +- **Path:** `WebServerRole -> ssm:* -> * (any instance)` +- **Controls present:** none +- **Controls removed:** ScopedDocument +- **Component:** `WebServerRole` +- **Transitive reach:** remote command execution on any instance +- **Template assertion:** ssm statement becomes ssm:* on Resource '*' + +Fleet-wide RCE via SSM from a role reachable through the instance. + + +## Benign commits — why each is a probe + +- **C3** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: tighten alb health check interval` — target group only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- role already has AmazonSSMManagedInstanceCore at C1 +- instance role is reachable from the app tier at C1 + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3, C5 +- **False-positive probes:** C3 + +- **State-vs-event probes:** C5 + +EC2/SSM RCE escalation; the scoped session doc at Mild becomes fleet-wide at Severe. + +## 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/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..dd093918 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:StartSession'], + resources: ['arn:aws:ssm:*:*:document/SSM-SessionManagerRunShell'], // scoped session doc + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/commit.json new file mode 100644 index 00000000..c7bb9fb2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "7acaaf8", + "full_sha": "7acaaf80819c809fd0fe4007e6e82d985e52dd4c", + "parent": "0cac3cd", + "message": "feat: allow session manager shell on the web server", + "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 | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/diff.patch new file mode 100644 index 00000000..375d646e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C2/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..dd09391 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,6 +260,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + ++ this.webServerRole.addToPolicy(new iam.PolicyStatement({ ++ actions: ['ssm:StartSession'], ++ resources: ['arn:aws:ssm:*:*:document/SSM-SessionManagerRunShell'], // scoped session doc ++ })); ++ + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..0af7aa2e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:StartSession'], + resources: ['arn:aws:ssm:*:*:document/SSM-SessionManagerRunShell'], // scoped session doc + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/commit.json new file mode 100644 index 00000000..7eb0d070 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "3c3c4ef", + "full_sha": "3c3c4efd6c36078d2354211cd7f22824b9d9dd1a", + "parent": "7acaaf8", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-18T10: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/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/diff.patch new file mode 100644 index 00000000..80043905 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C3/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index dd09391..0af7aa2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..d5c2c7df --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:*'], + resources: ['*'], // full ssm + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/commit.json new file mode 100644 index 00000000..8d43eede --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "e97003b", + "full_sha": "e97003ba1132b4856dc5476dde7cc000ae3d7016", + "parent": "3c3c4ef", + "message": "fix: grant full ssm to unblock fleet automation", + "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/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/diff.patch new file mode 100644 index 00000000..72491e90 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C4/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index 0af7aa2..d5c2c7d 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -266,8 +266,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ +- actions: ['ssm:StartSession'], +- resources: ['arn:aws:ssm:*:*:document/SSM-SessionManagerRunShell'], // scoped session doc ++ actions: ['ssm:*'], ++ resources: ['*'], // full ssm + })); + + // --- Outputs --- \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..9d3c0f7f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:*'], + resources: ['*'], // full ssm + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/commit.json new file mode 100644 index 00000000..60e6b477 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "d6767c0", + "full_sha": "d6767c06537fcb2c23235b1d5211b4117edfd617", + "parent": "e97003b", + "message": "chore: tighten alb health check interval", + "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": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/diff.patch new file mode 100644 index 00000000..008f6585 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d5c2c7d..9d3c0f7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -216,7 +216,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/provenance.json new file mode 100644 index 00000000..a22f4d47 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/commits/provenance.json @@ -0,0 +1,77 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "7acaaf8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "3c3c4ef", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "e97003b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d6767c0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/metadata.yaml new file mode 100644 index 00000000..cf43929a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/metadata.yaml @@ -0,0 +1,102 @@ +benchmark: G2 +family: Privilege Drift +drift_name: SSM Session to Full SSM +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The web server role gains ssm:StartSession on a named document (C2, Mild), then full\ + \ ssm:* on '*' (C4, Severe) \u2014 command execution on any instance." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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 grant: StartSession on a doc -> ssm:* on ''*''.' +commit_count: 5 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 7acaaf8 + label: V2 + message: 'feat: allow session manager shell on the web server' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: add + mechanism: ssm_start_session_scoped + path: WebServerRole -> ssm:StartSession -> run-shell document + controls_present: + - ScopedDocument + controls_absent: [] + component: WebServerRole + transitive_reach: + - interactive shell on this instance + construct_ids: + - WebServerRole + template_assertion: policy ssm:StartSession Resource = a named SSM document ARN + note: Session access, document-scoped. Mild. + C3: + sha: 3c3c4ef + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C4: + sha: e97003b + label: V3 + message: 'fix: grant full ssm to unblock fleet automation' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: ssm_full + path: WebServerRole -> ssm:* -> * (any instance) + controls_present: [] + controls_removed: + - ScopedDocument + component: WebServerRole + transitive_reach: + - remote command execution on any instance + construct_ids: + - WebServerRole + template_assertion: ssm statement becomes ssm:* on Resource '*' + note: Fleet-wide RCE via SSM from a role reachable through the instance. + C5: + sha: d6767c0 + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: target group only +baseline_known_weaknesses: +- role already has AmazonSSMManagedInstanceCore at C1 +- instance role is reachable from the app tier at C1 +scoring: + drift_commits: + - C2 + - C4 + benign_commits: + - C3 + - C5 + false_positive_probes: + - C3 + state_vs_event_probes: + - C5 + severity_axis: SSM grant breadth (scoped session -> ssm:*) + note: EC2/SSM RCE escalation; the scoped session doc at Mild becomes fleet-wide at Severe. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/repository new file mode 160000 index 00000000..d6767c06 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/repository @@ -0,0 +1 @@ +Subproject commit d6767c06537fcb2c23235b1d5211b4117edfd617 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/template_analysis.json new file mode 100644 index 00000000..6e8e87ce --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G2/template_analysis.json @@ -0,0 +1,159 @@ +{ + "benchmark": "G2", + "drift_name": "SSM Session to Full SSM", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "7acaaf8", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "3c3c4ef", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "e97003b", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:ssm:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "d6767c0", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE:ssm:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/README.md new file mode 100644 index 00000000..f7ddce14 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/README.md @@ -0,0 +1,159 @@ +# G3 — PassRole Privilege Escalation + +**Family:** Privilege Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The web server role gains EC2 describe permissions (C6, Mild), then ec2:RunInstances and iam:PassRole (C9, Severe) — it can launch instances with any role. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +EC2/IAM grant: describe-only -> RunInstances + PassRole. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `43cabbb` | VB | None | None | 38 | 2 | +| C6 | `1fa9586` | V2 | **Mild** | Mild | 38 | **8** | +| C7 | `9719ab5` | VB | None | Mild | 38 | 254 | +| C8 | `085bce3` | VB | None | Mild | 38 | 2 | +| C9 | `e91bf84` | V3 | **Severe** | Severe | 38 | **4** | +| C10 | `51f6d03` | VB | None | Severe | 42 | 139 | +| C11 | `bb9e20c` | VB | None | Severe | 42 | 5 | +| C12 | `cfb1602` | VB | None | Severe | 42 | **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 254 lines**. +The smallest drift-bearing diff is **C9 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: let the app read ec2 inventory for self-registration` + +- **Mechanism:** `ec2_read_only` +- **Path:** `WebServerRole -> ec2:Describe* -> *` +- **Controls present:** ReadOnlyEc2 +- **Component:** `WebServerRole` +- **Transitive reach:** EC2 inventory metadata +- **Template assertion:** policy with ec2:DescribeInstances/DescribeTags + +Read-only EC2 visibility. Mild. + +### C9 — V3, Severe + +`fix: allow the app to launch worker instances` + +- **Mechanism:** `passrole_escalation` +- **Path:** `WebServerRole -> ec2:RunInstances + iam:PassRole -> any role` +- **Controls present:** none +- **Controls removed:** ReadOnlyEc2 +- **Component:** `WebServerRole` +- **Transitive reach:** launch instances assuming any role +- **Template assertion:** statement actions become ec2:RunInstances + iam:PassRole + +Classic PassRole privilege escalation from a reachable instance role. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C5** `chore: widen cloudfront price class` — edge coverage only +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: grow root volume to 50gb` — block device size only +- **C10** `chore: enable vpc flow logs` — security improvement decoy +- **C11** `chore: add vpc id stack output` — cosmetic output +- **C12** `docs: add iam runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- no permissions boundary on the web server role at C1 +- instance role reachable via SSRF if IMDSv2 is later disabled + +## Scoring + +- **Drift commits:** C6, C9 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C10, C11, C12 +- **False-positive probes:** C7, C10 + +- **State-vs-event probes:** C11, C12 + +Privilege-escalation axis; the Severe two-action swap is a tiny diff. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..9d814a2f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:RunInstances', 'iam:PassRole'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/commit.json new file mode 100644 index 00000000..596223c1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "51f6d03", + "full_sha": "51f6d03aaa22885ddf7c7bed19ec263bd3e89fe3", + "parent": "e91bf84", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/diff.patch new file mode 100644 index 00000000..2991a790 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a0691c2..9d814a2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..c72d783e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:RunInstances', 'iam:PassRole'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/commit.json new file mode 100644 index 00000000..1ef7fc8d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "bb9e20c", + "full_sha": "bb9e20cf1ae116e3655c3bbdc702512bbdeef800", + "parent": "51f6d03", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/diff.patch new file mode 100644 index 00000000..f871ea01 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 9d814a2..c72d783 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -273,6 +273,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + })); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..c72d783e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:RunInstances', 'iam:PassRole'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/commit.json new file mode 100644 index 00000000..cc99f366 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "cfb1602", + "full_sha": "cfb16021f6d2439d85c92054ab8b5df4538ddd66", + "parent": "bb9e20c", + "message": "docs: add iam 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/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..52a31153 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/commit.json new file mode 100644 index 00000000..e85d8e9a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "43cabbb", + "full_sha": "43cabbb760ea90783e1d82b748fa13c3d6350e33", + "parent": "8101537", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/diff.patch new file mode 100644 index 00000000..51632666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..52a3115 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -256,7 +256,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b3cec640 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:DescribeInstances', 'ec2:DescribeTags'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/commit.json new file mode 100644 index 00000000..34ef1849 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "1fa9586", + "full_sha": "1fa95866a39541903fe6ed1d4652fa7d441f8ce6", + "parent": "43cabbb", + "message": "feat: let the app read ec2 inventory for self-registration", + "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 | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/diff.patch new file mode 100644 index 00000000..475c4582 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 52a3115..b3cec64 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,6 +260,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + ++ this.webServerRole.addToPolicy(new iam.PolicyStatement({ ++ actions: ['ec2:DescribeInstances', 'ec2:DescribeTags'], ++ resources: ['*'], ++ })); ++ + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..ef554ee8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:DescribeInstances', 'ec2:DescribeTags'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/commit.json new file mode 100644 index 00000000..bdd4df8b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "9719ab5", + "full_sha": "9719ab55393dc980a3d4b057a2c00d0791014af2", + "parent": "1fa9586", + "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/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/diff.patch new file mode 100644 index 00000000..b8f8a155 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index b3cec64..ef554ee 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..990d7567 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:DescribeInstances', 'ec2:DescribeTags'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/commit.json new file mode 100644 index 00000000..af83231f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "085bce3", + "full_sha": "085bce301b88a60a2790c3b1340f98e50713f175", + "parent": "9719ab5", + "message": "chore: grow root volume to 50gb", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/diff.patch new file mode 100644 index 00000000..79c557b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index ef554ee..990d756 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -154,7 +154,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..a0691c2b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ec2:RunInstances', 'iam:PassRole'], + resources: ['*'], + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/commit.json new file mode 100644 index 00000000..75f2c417 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "e91bf84", + "full_sha": "e91bf842870b2709210a16ae84e91d95fc00aaa5", + "parent": "085bce3", + "message": "fix: allow the app to launch worker instances", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/diff.patch new file mode 100644 index 00000000..155dfec7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 990d756..a0691c2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -266,7 +266,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + this.webServerRole.addToPolicy(new iam.PolicyStatement({ +- actions: ['ec2:DescribeInstances', 'ec2:DescribeTags'], ++ actions: ['ec2:RunInstances', 'iam:PassRole'], + resources: ['*'], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/provenance.json new file mode 100644 index 00000000..2a08b511 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/commits/provenance.json @@ -0,0 +1,182 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "51f6d03", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "bb9e20c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "cfb1602", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "43cabbb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "1fa9586", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "9719ab5", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "085bce3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "e91bf84", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/metadata.yaml new file mode 100644 index 00000000..c9ee1ccb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/metadata.yaml @@ -0,0 +1,167 @@ +benchmark: G3 +family: Privilege Drift +drift_name: PassRole Privilege Escalation +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The web server role gains EC2 describe permissions (C6, Mild), then ec2:RunInstances\ + \ and iam:PassRole (C9, Severe) \u2014 it can launch instances with any role." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'EC2/IAM grant: describe-only -> RunInstances + PassRole.' +commit_count: 12 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: 43cabbb + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: edge coverage only + C6: + sha: 1fa9586 + label: V2 + message: 'feat: let the app read ec2 inventory for self-registration' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: add + mechanism: ec2_read_only + path: WebServerRole -> ec2:Describe* -> * + controls_present: + - ReadOnlyEc2 + controls_absent: [] + component: WebServerRole + transitive_reach: + - EC2 inventory metadata + construct_ids: + - WebServerRole + template_assertion: policy with ec2:DescribeInstances/DescribeTags + note: Read-only EC2 visibility. Mild. + C7: + sha: 9719ab5 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: 085bce3 + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C9: + sha: e91bf84 + label: V3 + message: 'fix: allow the app to launch worker instances' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: passrole_escalation + path: WebServerRole -> ec2:RunInstances + iam:PassRole -> any role + controls_present: [] + controls_removed: + - ReadOnlyEc2 + component: WebServerRole + transitive_reach: + - launch instances assuming any role + construct_ids: + - WebServerRole + template_assertion: statement actions become ec2:RunInstances + iam:PassRole + note: Classic PassRole privilege escalation from a reachable instance role. + C10: + sha: 51f6d03 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C11: + sha: bb9e20c + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C12: + sha: cfb1602 + label: VB + message: 'docs: add iam runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- no permissions boundary on the web server role at C1 +- instance role reachable via SSRF if IMDSv2 is later disabled +scoring: + drift_commits: + - C6 + - C9 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C10 + - C11 + - C12 + false_positive_probes: + - C7 + - C10 + state_vs_event_probes: + - C11 + - C12 + severity_axis: EC2/IAM action escalation (describe -> RunInstances + PassRole) + note: Privilege-escalation axis; the Severe two-action swap is a tiny diff. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/repository new file mode 160000 index 00000000..cfb16021 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/repository @@ -0,0 +1 @@ +Subproject commit cfb16021f6d2439d85c92054ab8b5df4538ddd66 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/template_analysis.json new file mode 100644 index 00000000..bc99746b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G3/template_analysis.json @@ -0,0 +1,368 @@ +{ + "benchmark": "G3", + "drift_name": "PassRole Privilege Escalation", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "43cabbb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "1fa9586", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 8, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "9719ab5", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "085bce3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "e91bf84", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "51f6d03", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "bb9e20c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "cfb1602", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [ + "WebServerRoleDefaultPolicy9AFE19CE" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/README.md new file mode 100644 index 00000000..2830d55d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/README.md @@ -0,0 +1,135 @@ +# G4 — PowerUser to Administrator + +**Family:** Privilege Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The web server role is granted PowerUserAccess (C2, Mild), then AdministratorAccess (C3, Severe). The severe drift is a single managed-policy name change. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Managed policy on the instance role: none -> 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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `5fa93db` | V2 | **Mild** | Mild | 38 | **12** | +| C3 | `9594f27` | V3 | **Severe** | Severe | 38 | **2** | +| C4 | `b386d6b` | VB | None | Severe | 38 | 254 | + +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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: grant instance role poweruser for a migration` + +- **Mechanism:** `managed_policy_poweruser` +- **Path:** `WebServerRole -> PowerUserAccess` +- **Controls present:** NoIamActions +- **Component:** `WebServerRole` +- **Transitive reach:** most AWS services except IAM +- **Template assertion:** role ManagedPolicyArns gains PowerUserAccess + +Very broad but stops short of IAM. Mild. + +### C3 — V3, Severe + +`fix: bump instance role to administrator to unblock` + +- **Mechanism:** `managed_policy_administrator` +- **Path:** `WebServerRole -> AdministratorAccess` +- **Controls present:** none +- **Controls removed:** NoIamActions +- **Component:** `WebServerRole` +- **Transitive reach:** every action on every resource +- **Template assertion:** ManagedPolicyArns PowerUserAccess -> AdministratorAccess + +Full admin on a role reachable from the app tier. Two-character diff. + + +## Benign commits — why each is a probe + +- **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. + +- instance role reachable from the app tier at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 + +## Scoring + +- **Drift commits:** C2, C3 +- **Benign commits:** C4 +- **False-positive probes:** C4 + +- **State-vs-event probes:** + +Cross-suite AdministratorAccess pair with T2 (cross-account role). + +## 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/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..e8e04bf4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/cdk.ts @@ -0,0 +1,332 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/commit.json new file mode 100644 index 00000000..68d542c8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "5fa93db", + "full_sha": "5fa93db3db92d7b0079147a7dbd3fe53a3e14d25", + "parent": "0cac3cd", + "message": "feat: grant instance role poweruser for a migration", + "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 | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/diff.patch new file mode 100644 index 00000000..3f94c314 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..e8e04bf 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,6 +260,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + ++ this.webServerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')); ++ + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..cb3a938d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/cdk.ts @@ -0,0 +1,332 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/commit.json new file mode 100644 index 00000000..3c4bda11 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "9594f27", + "full_sha": "9594f27358561c89fc1f47601cd4c29f2be9054a", + "parent": "5fa93db", + "message": "fix: bump instance role to administrator to unblock", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10: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/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/diff.patch new file mode 100644 index 00000000..ac4543fc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index e8e04bf..cb3a938 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,7 +260,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + +- this.webServerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')); ++ this.webServerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..f022cb00 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + this.webServerRole.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/commit.json new file mode 100644 index 00000000..e870a40f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "b386d6b", + "full_sha": "b386d6bb9f7da7bd90b83e29ca9538b7bfd5600e", + "parent": "9594f27", + "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": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/diff.patch new file mode 100644 index 00000000..7d0d9013 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index cb3a938..f022cb0 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/provenance.json new file mode 100644 index 00000000..b2987ba7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/commits/provenance.json @@ -0,0 +1,62 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "5fa93db", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "9594f27", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "b386d6b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/metadata.yaml new file mode 100644 index 00000000..78fc7065 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/metadata.yaml @@ -0,0 +1,92 @@ +benchmark: G4 +family: Privilege Drift +drift_name: PowerUser to Administrator +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: The web server role is granted PowerUserAccess (C2, Mild), then AdministratorAccess + (C3, Severe). The severe drift is a single managed-policy name change. +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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 instance role: none -> PowerUser -> Administrator.' +commit_count: 4 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 5fa93db + label: V2 + message: 'feat: grant instance role poweruser for a migration' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: add + mechanism: managed_policy_poweruser + path: WebServerRole -> PowerUserAccess + controls_present: + - NoIamActions + controls_absent: [] + component: WebServerRole + transitive_reach: + - most AWS services except IAM + construct_ids: + - WebServerRole + template_assertion: role ManagedPolicyArns gains PowerUserAccess + note: Very broad but stops short of IAM. Mild. + C3: + sha: 9594f27 + label: V3 + message: 'fix: bump instance role to administrator to unblock' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: managed_policy_administrator + path: WebServerRole -> AdministratorAccess + controls_present: [] + controls_removed: + - NoIamActions + component: WebServerRole + transitive_reach: + - every action on every resource + construct_ids: + - WebServerRole + template_assertion: ManagedPolicyArns PowerUserAccess -> AdministratorAccess + note: Full admin on a role reachable from the app tier. Two-character diff. + C4: + sha: b386d6b + 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: +- instance role reachable from the app tier at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 +scoring: + drift_commits: + - C2 + - C3 + benign_commits: + - C4 + false_positive_probes: + - C4 + state_vs_event_probes: [] + severity_axis: managed-policy breadth on the instance role + note: Cross-suite AdministratorAccess pair with T2 (cross-account role). diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/repository new file mode 160000 index 00000000..b386d6bb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/repository @@ -0,0 +1 @@ +Subproject commit b386d6bb9f7da7bd90b83e29ca9538b7bfd5600e diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/template_analysis.json new file mode 100644 index 00000000..9ba6ae3d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/G4/template_analysis.json @@ -0,0 +1,125 @@ +{ + "benchmark": "G4", + "drift_name": "PowerUser to Administrator", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "5fa93db", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "PowerUserAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "9594f27", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "b386d6b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/PULL_REQUEST.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/PULL_REQUEST.md new file mode 100644 index 00000000..a80fe90d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/PULL_REQUEST.md @@ -0,0 +1,148 @@ +# CDK Drift Benchmark Suite — 21 benchmarks across 5 families + +## Summary + +Adds `benchmarks-image/` — 21 independent parallel evolutions of the +`simple-image-upload-download-website` CDK stack, one per drift scenario. Every benchmark +is a real git repository with real commits (4 to 14 each, 4 minimum) and a per-commit +synthesized CloudFormation template. All 21 share commit C1 = `0cac3cd` byte-for-byte; +divergence starts at C2. + +Companion to the text-paste (25), containerized-microservices (30), support-ticketing (28) +and Bedrock-chatbot (32) suites. This baseline is a classic image upload/download web app: +a public ALB in front of an EC2 web server (private subnet, SSM, IMDSv2, encrypted EBS), an +S3 image bucket (block-public-access, versioned, SSE, enforceSSL), a CloudFront distribution, +and an EC2 instance role with S3 read/write for presigned URLs. + +**Total: 21 benchmarks, 194 commits, 31 MB, 100% verified and typechecked.** Five suites +now total 25 + 30 + 28 + 32 + 21 = 136 benchmarks. + +## Sized to the architecture, not to a number + +Each family holds only the drift mechanisms this stack genuinely supports — no padding to +hit a round total. The three reachability drifts (SG bypass, public subnet, IMDSv2/SSRF) +are the real ways internal compute here becomes reachable; E is the largest family because +S3 is the data store and carries several distinct controls (BlockPublicAccess, bucket +policy, at-rest encryption, CORS) alongside EBS and CloudFront. + +## Drift families + +Each family holds severity to a single axis; 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 SG bypasses ALB, R2 public-subnet SSH, R3 IMDSv2 disabled (SSRF) | +| **G — Privilege** | instance-role IAM breadth | G1 S3 wildcard, G2 SSM full (RCE), G3 PassRole escalation, G4 Administrator | +| **E — Exposure** | existing surface loses restrictions | E1 S3 BlockPublicAccess, E2 S3 bucket policy, E3 S3 encryption, E4 S3 CORS `*`, E5 EBS encryption, E6 CloudFront HTTP | +| **D — Dependency / Integration** | third-party enters the image path | D1 external resizer, D2 external CDN origin, D3 upload webhook signature, D4 malware-scan fail-open | +| **T — Trust Boundary** | privilege/data crosses account boundary | T1 bucket cross-account, T2 cross-account role→Admin, T3 VPC peering, T4 cross-account replication | + +Cross-family pairs are scoreable — **G4↔T2** (`AdministratorAccess`: internal vs +cross-account), and the S3 `BlockPublicAccess` drift **E1** is a **quintuple-baseline oracle** +(paste E5 / ecs E3 / support E1 / chatbot E1 / image E1). Baseline-distinctive axes: **R3** +IMDSv2→IMDSv1 (EC2 SSRF-to-credential) and **E4** S3 CORS `*` (browser-upload exposure). + +## Commit log structure + +Every benchmark's `repository/` is a genuine git repo (R1 shown): + +``` +9880630 chore: extend gitignore # VB benign +1bfcb9a docs: add networking runbook # VB benign +9f6fe19 chore: enable vpc flow logs # VB benign +ce933aa fix: expose web server port for an external uptime check # V3 Severe (R1) +04e358a chore: apply cost-allocation tags # VB benign +1e52b48 feat: allow in-vpc clients to reach the web server directly # V2 Mild +10163b2 chore: extend image retention to two years # VB benign +d4e7e9b chore: tighten alb health check interval # VB benign +0cac3cd feat: simple image upload/download website baseline # V1 (shared) +``` + +Author, date and identity are pinned so SHAs are reproducible. Every benchmark's C1 is +`0cac3cd`; divergence starts at C2. + +Commit-count distribution (no drift-position collision within a length): + +``` +count: 4 5 6 7 8 9 10 11 12 13 14 +benchmark: E1 G2 E5 R2 D1 E3 G1 T1 R1 T2 D2 E2 R3 T3 D3 G3 E4 T4 D4 E6 + G4 +``` + +## Per-benchmark layout + +Each `benchmarks-image//` mirrors the source dataset's `/cdk/` folder shape: + +``` +R1/ +├── repository/ # real working tree + real .git +├── commits/ +│ ├── C1..CN/ +│ │ ├── cdk/ # same layout as simple-image-upload-download-website/cdk/ +│ │ │ ├── cdk.ts, cdk.json, package.json, tsconfig.json +│ │ │ └── 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 # generated spec +``` + +Tooling written against `simple-image-upload-download-website/cdk/cdk.out/*.template.json` +works against `benchmarks-image/R1/commits/C5/cdk/cdk.out/*.template.json` with only the +root path changed. Resource counts run 37 (baseline) → 44. + +## 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, +transitive reach, and a `template_assertion` string. `commit_severity` is the drift +introduced by that commit; `cumulative_severity` is the standing risk at that commit. + +## Suite property worth naming + +Template diff size is deliberately anti-correlated with severity. Benign cost-allocation +tag commits produce the largest diffs (254–270 lines); severe commits typically flip one +field: + +| | Severe | benign (biggest) | +|---|---|---| +| T4 | 2 lines (replication resource → `arn:aws:s3:::*`) | 270 lines | +| T2 | 2 lines (cross-account role → Administrator) | 268 lines | +| G4 | 2 lines (PowerUser → Administrator) | 254 lines | +| D4 | 2 lines (`AV_FAIL_OPEN` false → true) | 254 lines | +| E4 | 2 lines (CORS allowedOrigins → `*`) | 254 lines | +| R3 | 20 lines (IMDSv2 required → optional) | 254 lines | + +Any detector triaging by diff magnitude scores worse than chance. + +## Quintuple-baseline oracle (free ground truth) + +The source dataset's image bucket uses `BlockPublicAccess.BLOCK_ALL`; **E1** drifts it to +all-false — the same mechanism as `benchmarks/E5`, `benchmarks-ecs/E3`, +`benchmarks-support/E1` and `benchmarks-chatbot/E1`. Detector performance on synthetic vs. +real curated drift for one mechanism is now comparable across **five** different stacks. + +## Known limitation + +The baseline ships no `package.json` at the cdk root, so no `aws-cdk-lib` version is +declared; all templates were synthesized with 2.150.0 and commits flag `version_match: +false` in `provenance.json` (declaring the 2.100.0 default supplied at import). Within a +benchmark every template comes from one library version, so cross-commit diffs are clean. +No source normalization was needed — the baseline compiles and synthesizes under 2.150.0 as +written (only a harmless `keyName` deprecation warning). Because raw `ts-node` synth does not +read `cdk.json`, the required context (applicationName, environmentType, instance type, key +pair, and the encryption feature flags) is injected via `synth_context.json`; a key pair is +supplied so the SSH-0.0.0.0/0 ingress present in the source is realized at C1. + +## Not included + +`threat-model.json`, `canonical_ir.json`, the `7.*` graph IRs, `architecture.png`, +`diagram.py`, the `v1/` and `v2/` variant folders and their checkov/threat-model artifacts +from the source dataset 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 the instance is exposed or the bucket goes public, which is worse than having none. +Run your own pipeline over `commits/C*/cdk/` to produce them. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/README.md new file mode 100644 index 00000000..2b22c1b8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/README.md @@ -0,0 +1,150 @@ +# R1 — Web Server SG Bypasses ALB + +**Family:** Architectural Reachability Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The EC2 web server security group opens tcp/80 to the VPC CIDR (C4, Mild), then to 0.0.0.0/0 (C6, Severe) — clients reach the instance directly, bypassing the ALB and CloudFront. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +webServerSg ingress on tcp/80: ALB-only -> VPC CIDR -> 0.0.0.0/0. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `1e52b48` | V2 | **Mild** | Mild | 38 | **12** | +| C5 | `04e358a` | VB | None | Mild | 38 | 254 | +| C6 | `ce933aa` | V3 | **Severe** | Severe | 38 | **9** | +| C7 | `9f6fe19` | VB | None | Severe | 42 | 139 | +| C8 | `1bfcb9a` | VB | None | Severe | 42 | **0** | +| C9 | `9880630` | VB | None | Severe | 42 | **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 254 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: allow in-vpc clients to reach the web server directly` + +- **Mechanism:** `ec2_sg_vpc_ingress` +- **Path:** `VPC clients -> tcp/80 -> WebServerInstance` +- **Controls present:** VpcCidrScoped +- **Component:** `WebServerSg` +- **Transitive reach:** the web server, from any VPC host +- **Template assertion:** SecurityGroupIngress tcp/80 from the VPC CIDR appears + +Direct-to-instance path opens but stays inside the VPC. Mild. + +### C6 — V3, Severe + +`fix: expose web server port for an external uptime check` + +- **Mechanism:** `ec2_sg_public_ingress` +- **Path:** `internet -> tcp/80 -> WebServerInstance (ALB + CloudFront bypassed)` +- **Controls present:** none +- **Controls removed:** VpcCidrScoped +- **Component:** `WebServerSg` +- **Transitive reach:** the web server, from anywhere +- **Template assertion:** SecurityGroupIngress tcp/80 CidrIp 0.0.0.0/0 + +Direct internet reach; the ALB and CDN are no longer in the path. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: enable vpc flow logs` — security improvement decoy +- **C8** `docs: add networking runbook` — no template change 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. + +- albSg 0.0.0.0/0 on 80/443 at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7, C8, C9 +- **False-positive probes:** C5, C7 + +- **State-vs-event probes:** C8, C9 + +Bypass of the ALB+CloudFront path; C7 adds flow logs that observe but don't block. + +## 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/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..86ac728c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/cdk.ts @@ -0,0 +1,331 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + webServerSg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(80), 'Direct HTTP from VPC'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/commit.json new file mode 100644 index 00000000..69857ed1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "1e52b48", + "full_sha": "1e52b48d0da1b1d97e6e338cbae473cfcd3d4526", + "parent": "10163b2", + "message": "feat: allow in-vpc clients to reach the web server directly", + "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 | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/diff.patch new file mode 100644 index 00000000..d571dfdd --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C4/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..86ac728 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -84,6 +84,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); ++ webServerSg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(80), 'Direct HTTP from VPC'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..92302695 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/cdk.ts @@ -0,0 +1,336 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + webServerSg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(80), 'Direct HTTP from VPC'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/commit.json new file mode 100644 index 00000000..95afd26f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "04e358a", + "full_sha": "04e358a75fce645bc146ca666f5e59433b0f6e3e", + "parent": "1e52b48", + "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/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/diff.patch new file mode 100644 index 00000000..6a2df3be --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 86ac728..9230269 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..c53d8888 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/cdk.ts @@ -0,0 +1,336 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Direct HTTP from internet'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/commit.json new file mode 100644 index 00000000..7ab580f2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "ce933aa", + "full_sha": "ce933aa4cf0df9e5c76a155598399a0f9928d57b", + "parent": "04e358a", + "message": "fix: expose web server port for an external uptime check", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/diff.patch new file mode 100644 index 00000000..86521056 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 9230269..c53d888 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -89,7 +89,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); +- webServerSg.addIngressRule(ec2.Peer.ipv4(vpc.vpcCidrBlock), ec2.Port.tcp(80), 'Direct HTTP from VPC'); ++ webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Direct HTTP from internet'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..df63f5a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Direct HTTP from internet'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/commit.json new file mode 100644 index 00000000..b811072b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "9f6fe19", + "full_sha": "9f6fe19349e920be6902db72fc4275b0b8307352", + "parent": "ce933aa", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/diff.patch new file mode 100644 index 00000000..a8e43913 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index c53d888..df63f5a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..df63f5a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Direct HTTP from internet'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/commit.json new file mode 100644 index 00000000..e0ecc819 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "1bfcb9a", + "full_sha": "1bfcb9a44869dec8c6a43c5d92f667dc64d83613", + "parent": "9f6fe19", + "message": "docs: add networking 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/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..df63f5a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Direct HTTP from internet'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/commit.json new file mode 100644 index 00000000..43ccb80c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "9880630", + "full_sha": "9880630f917103d85df5957e413ca3f8459b4ace", + "parent": "1bfcb9a", + "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/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/C9/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/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/simple-image-upload-download-website/versionzied_gt/R1/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/provenance.json new file mode 100644 index 00000000..3f9b6872 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "1e52b48", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "04e358a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "ce933aa", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "9f6fe19", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "1bfcb9a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "9880630", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/metadata.yaml new file mode 100644 index 00000000..97c9a2ff --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/metadata.yaml @@ -0,0 +1,141 @@ +benchmark: R1 +family: Architectural Reachability Drift +drift_name: Web Server SG Bypasses ALB +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The EC2 web server security group opens tcp/80 to the VPC CIDR (C4, Mild), then to\ + \ 0.0.0.0/0 (C6, Severe) \u2014 clients reach the instance directly, bypassing the ALB and CloudFront." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'webServerSg ingress on tcp/80: ALB-only -> VPC CIDR -> 0.0.0.0/0.' +commit_count: 9 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 1e52b48 + label: V2 + message: 'feat: allow in-vpc clients to reach the web server directly' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: ec2_sg_vpc_ingress + path: VPC clients -> tcp/80 -> WebServerInstance + controls_present: + - VpcCidrScoped + controls_absent: + - AlbOnly + component: WebServerSg + transitive_reach: + - the web server, from any VPC host + construct_ids: + - WebServerSg + template_assertion: SecurityGroupIngress tcp/80 from the VPC CIDR appears + note: Direct-to-instance path opens but stays inside the VPC. Mild. + C5: + sha: 04e358a + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: ce933aa + label: V3 + message: 'fix: expose web server port for an external uptime check' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: ec2_sg_public_ingress + path: internet -> tcp/80 -> WebServerInstance (ALB + CloudFront bypassed) + controls_present: [] + controls_removed: + - VpcCidrScoped + component: WebServerSg + transitive_reach: + - the web server, from anywhere + construct_ids: + - WebServerSg + template_assertion: SecurityGroupIngress tcp/80 CidrIp 0.0.0.0/0 + note: Direct internet reach; the ALB and CDN are no longer in the path. + C7: + sha: 9f6fe19 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C8: + sha: 1bfcb9a + label: VB + message: 'docs: add networking runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C9: + sha: '9880630' + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- albSg 0.0.0.0/0 on 80/443 at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 +scoring: + drift_commits: + - C4 + - C6 + benign_commits: + - C2 + - C3 + - C5 + - C7 + - C8 + - C9 + false_positive_probes: + - C5 + - C7 + state_vs_event_probes: + - C8 + - C9 + severity_axis: webServerSg ingress breadth on tcp/80 + note: Bypass of the ALB+CloudFront path; C7 adds flow logs that observe but don't block. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/repository new file mode 160000 index 00000000..9880630f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/repository @@ -0,0 +1 @@ +Subproject commit 9880630f917103d85df5957e413ca3f8459b4ace diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/template_analysis.json new file mode 100644 index 00000000..497bd029 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R1/template_analysis.json @@ -0,0 +1,267 @@ +{ + "benchmark": "R1", + "drift_name": "Web Server SG Bypasses ALB", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "1e52b48", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "04e358a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "ce933aa", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 9, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "9f6fe19", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "1bfcb9a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "9880630", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/README.md new file mode 100644 index 00000000..b3d4db88 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/README.md @@ -0,0 +1,142 @@ +# R2 — Web Server in Public Subnet + +**Family:** Architectural Reachability Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The web server moves to a public subnet with SSH scoped to a corporate CIDR (C3, Mild), then SSH is opened to 0.0.0.0/0 (C5, Severe) — a public-IP instance with internet-reachable SSH. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Instance placement + SSH: private/key-gated -> public subnet, corp SSH -> public SSH. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `a807e44` | V2 | **Mild** | Mild | 38 | **6** | +| C4 | `7ecac98` | VB | None | Mild | 38 | 254 | +| C5 | `8a7a600` | V3 | **Severe** | Severe | 38 | **4** | +| C6 | `913f4ce` | VB | None | Severe | 38 | **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 254 lines**. +The smallest drift-bearing diff is **C5 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: move web server to public subnet for direct debugging` + +- **Mechanism:** `ec2_public_subnet_corp_ssh` +- **Path:** `corp CIDR -> tcp/22 -> WebServerInstance (public IP)` +- **Controls present:** CorpCidrSsh +- **Controls removed:** PrivateSubnet +- **Component:** `WebServerInstance` +- **Transitive reach:** SSH from the corporate network +- **Template assertion:** instance SubnetId becomes a public subnet; SSH ingress from a corp CIDR + +Public placement, but SSH scoped. Mild. + +### C5 — V3, Severe + +`fix: open ssh from anywhere for the remote contractor` + +- **Mechanism:** `ec2_public_ssh_open` +- **Path:** `internet -> tcp/22 -> WebServerInstance (public IP)` +- **Controls present:** none +- **Controls removed:** CorpCidrSsh +- **Component:** `WebServerSg` +- **Transitive reach:** SSH from anywhere to a public instance +- **Template assertion:** SSH SecurityGroupIngress CidrIp becomes 0.0.0.0/0 + +Internet-reachable SSH on a public-IP box. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C6** `docs: add access runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- SSH rule is 0.0.0.0/0 but instance is private at C1 (latent) +- requireImdsv2 true at C1 limits SSRF + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4, C6 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C6 + +Public subnet activates the latent SSH 0.0.0.0/0 weakness; V2 scopes it, V3 reopens it. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..b9554755 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(22), 'Corp SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // moved to public subnet + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/commit.json new file mode 100644 index 00000000..9cdbc27e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "a807e44", + "full_sha": "a807e44c1abfe9a13cd1b7d0fc7c930b6f7fb00d", + "parent": "d4e7e9b", + "message": "feat: move web server to public subnet for direct debugging", + "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 | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/diff.patch new file mode 100644 index 00000000..ef532404 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..b955475 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -87,7 +87,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { +- webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); ++ webServerSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(22), 'Corp SSH access'); + } + + // --- IAM Role for EC2 Instance --- +@@ -143,7 +143,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { +- subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets ++ subnetType: ec2.SubnetType.PUBLIC, // moved to public subnet + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..b01b74f1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(22), 'Corp SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // moved to public subnet + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/commit.json new file mode 100644 index 00000000..5db9ff19 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "7ecac98", + "full_sha": "7ecac98d1bb4f4262c3635ec0cb93ceb9e1f409e", + "parent": "a807e44", + "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/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/diff.patch new file mode 100644 index 00000000..7ae61969 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index b955475..b01b74f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..bfab8ea2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Internet SSH to public instance'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // moved to public subnet + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/commit.json new file mode 100644 index 00000000..94a3961a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "8a7a600", + "full_sha": "8a7a60097555c829175dffee3a5b5c7b76847368", + "parent": "7ecac98", + "message": "fix: open ssh from anywhere for the remote contractor", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "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/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/diff.patch new file mode 100644 index 00000000..6185d7b8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b01b74f..bfab8ea 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -92,7 +92,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { +- webServerSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(22), 'Corp SSH access'); ++ webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Internet SSH to public instance'); + } + + // --- IAM Role for EC2 Instance --- \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..bfab8ea2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Internet SSH to public instance'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // moved to public subnet + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/commit.json new file mode 100644 index 00000000..9384943c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "913f4ce", + "full_sha": "913f4ce6e7cfa1b58a6d6f9ab0dd723d6956c3eb", + "parent": "8a7a600", + "message": "docs: add access 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/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/provenance.json new file mode 100644 index 00000000..4f29dce1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "a807e44", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "7ecac98", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8a7a600", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "913f4ce", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/metadata.yaml new file mode 100644 index 00000000..9a1a266a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/metadata.yaml @@ -0,0 +1,114 @@ +benchmark: R2 +family: Architectural Reachability Drift +drift_name: Web Server in Public Subnet +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "The web server moves to a public subnet with SSH scoped to a corporate CIDR (C3, Mild),\ + \ then SSH is opened to 0.0.0.0/0 (C5, Severe) \u2014 a public-IP instance with internet-reachable\ + \ SSH." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Instance placement + SSH: private/key-gated -> public subnet, corp SSH -> public SSH.' +commit_count: 6 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: a807e44 + label: V2 + message: 'feat: move web server to public subnet for direct debugging' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: ec2_public_subnet_corp_ssh + path: corp CIDR -> tcp/22 -> WebServerInstance (public IP) + controls_present: + - CorpCidrSsh + controls_removed: + - PrivateSubnet + component: WebServerInstance + transitive_reach: + - SSH from the corporate network + construct_ids: + - WebServerInstance + - WebServerSg + template_assertion: instance SubnetId becomes a public subnet; SSH ingress from a corp CIDR + note: Public placement, but SSH scoped. Mild. + C4: + sha: 7ecac98 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: 8a7a600 + label: V3 + message: 'fix: open ssh from anywhere for the remote contractor' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: ec2_public_ssh_open + path: internet -> tcp/22 -> WebServerInstance (public IP) + controls_present: [] + controls_removed: + - CorpCidrSsh + component: WebServerSg + transitive_reach: + - SSH from anywhere to a public instance + construct_ids: + - WebServerSg + template_assertion: SSH SecurityGroupIngress CidrIp becomes 0.0.0.0/0 + note: Internet-reachable SSH on a public-IP box. + C6: + sha: 913f4ce + label: VB + message: 'docs: add access runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- SSH rule is 0.0.0.0/0 but instance is private at C1 (latent) +- requireImdsv2 true at C1 limits SSRF +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + - C6 + false_positive_probes: + - C4 + state_vs_event_probes: + - C6 + severity_axis: instance placement and SSH ingress breadth + note: Public subnet activates the latent SSH 0.0.0.0/0 weakness; V2 scopes it, V3 reopens it. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/repository new file mode 160000 index 00000000..913f4ce6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/repository @@ -0,0 +1 @@ +Subproject commit 913f4ce6e7cfa1b58a6d6f9ab0dd723d6956c3eb diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/template_analysis.json new file mode 100644 index 00000000..c6930852 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R2/template_analysis.json @@ -0,0 +1,178 @@ +{ + "benchmark": "R2", + "drift_name": "Web Server in Public Subnet", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "a807e44", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 6, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "7ecac98", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "8a7a600", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "913f4ce", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/README.md new file mode 100644 index 00000000..9f68c1ef --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/README.md @@ -0,0 +1,156 @@ +# R3 — IMDSv2 Disabled (SSRF to Instance Role) + +**Family:** Architectural Reachability Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +A metadata-reachable debug helper is added to the instance (C5, Mild), then IMDSv2 enforcement is dropped (C8, Severe) — an SSRF in the app can read IMDSv1 and exfiltrate the instance role credentials. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Instance metadata exposure: IMDSv2 enforced -> debug proxy added -> IMDSv1 allowed. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `1b3b0ea` | V2 | **Mild** | Mild | 38 | **2** | +| C6 | `6a22990` | VB | None | Mild | 38 | 254 | +| C7 | `abcf43f` | VB | None | Mild | 38 | 2 | +| C8 | `990b3b6` | V3 | **Severe** | Severe | 37 | **20** | +| C9 | `2a025bb` | VB | None | Severe | 41 | 139 | +| C10 | `bb929c5` | VB | None | Severe | 41 | 5 | +| C11 | `d76aa26` | VB | None | Severe | 41 | **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 254 lines**. +The smallest drift-bearing diff is **C5 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: add metadata debug helper to instance bootstrap` + +- **Mechanism:** `metadata_debug_helper` +- **Path:** `app -> metadata debug helper (IMDSv2 still enforced)` +- **Controls present:** Imdsv2Enforced +- **Component:** `WebServerInstance` +- **Transitive reach:** a metadata-adjacent debug surface +- **Template assertion:** UserData gains a metadata proxy debug line + +SSRF surface introduced, but IMDSv2 still blocks token-less reads. Mild. + +### C8 — V3, Severe + +`fix: allow imdsv1 for an older sdk on the instance` + +- **Mechanism:** `imdsv2_disabled` +- **Path:** `SSRF -> IMDSv1 -> WebServerRole credentials -> S3` +- **Controls present:** none +- **Controls removed:** Imdsv2Enforced +- **Component:** `WebServerInstance` +- **Transitive reach:** instance-role credentials via SSRF +- **Template assertion:** LaunchTemplate MetadataOptions HttpTokens required -> optional + +IMDSv1 allowed; an app SSRF yields the role's S3 read/write credentials. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: grow root volume to 50gb` — block device size only +- **C9** `chore: enable vpc flow logs` — security improvement decoy +- **C10** `chore: add vpc id stack output` — cosmetic output +- **C11** `docs: add instance runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- WebServerRole has S3 read/write at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 + +## Scoring + +- **Drift commits:** C5, C8 +- **Benign commits:** C2, C3, C4, C6, C7, C9, C10, C11 +- **False-positive probes:** C6, C9 + +- **State-vs-event probes:** C10, C11 + +EC2-specific credential-theft axis; the Severe is a one-field MetadataOptions flip. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..bc33d080 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: false, // relaxed for legacy sdk compatibility + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/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/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/commit.json new file mode 100644 index 00000000..e3307597 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "bb929c5", + "full_sha": "bb929c5f8fd38f03e9f1e02aa6127d69eb88b16c", + "parent": "2a025bb", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/diff.patch new file mode 100644 index 00000000..aa696f3c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index f977472..bc33d08 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -268,6 +268,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..bc33d080 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: false, // relaxed for legacy sdk compatibility + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/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/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/commit.json new file mode 100644 index 00000000..37906cdf --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "d76aa26", + "full_sha": "d76aa26a9f3a7ea317f976057e98a094b026c3c2", + "parent": "bb929c5", + "message": "docs: add instance runbook", + "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/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..42cca6a5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/commit.json new file mode 100644 index 00000000..dae7a786 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "1b3b0ea", + "full_sha": "1b3b0eaf3d686ca96a53592f9c81f5f03600c8c9", + "parent": "8101537", + "message": "feat: add metadata debug helper to instance bootstrap", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/diff.patch new file mode 100644 index 00000000..b5262685 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..42cca6a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -159,7 +159,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ +- shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' ++ shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..6d87bdaa --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/commit.json new file mode 100644 index 00000000..ef7622f9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "6a22990", + "full_sha": "6a229908a7667873a9a4866bfb8add50816ed829", + "parent": "1b3b0ea", + "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/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/diff.patch new file mode 100644 index 00000000..a30227e6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 42cca6a..6d87bda 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..b20e8704 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/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/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/commit.json new file mode 100644 index 00000000..40e81b7d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "abcf43f", + "full_sha": "abcf43f7c8cda1dca3493b85b969368ae15582a3", + "parent": "6a22990", + "message": "chore: grow root volume to 50gb", + "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/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/diff.patch new file mode 100644 index 00000000..170c9d4b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 6d87bda..b20e870 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -154,7 +154,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..db727f14 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: false, // relaxed for legacy sdk compatibility + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/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/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/commit.json new file mode 100644 index 00000000..02851ccb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "990b3b6", + "full_sha": "990b3b6d48a6cb13d40c690d5efdcbc7b5728b2f", + "parent": "abcf43f", + "message": "fix: allow imdsv1 for an older sdk on the instance", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/diff.patch new file mode 100644 index 00000000..6b020624 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b20e870..db727f1 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -166,7 +166,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), +- requireImdsv2: true, // Enforce IMDSv2 for enhanced security ++ requireImdsv2: false, // relaxed for legacy sdk compatibility + }); + + // --- Application Load Balancer (ALB) --- \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..f9774726 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/cdk.ts @@ -0,0 +1,337 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs\n# DEBUG: start metadata proxy on :8080 for troubleshooting' + }), + requireImdsv2: false, // relaxed for legacy sdk compatibility + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/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/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/commit.json new file mode 100644 index 00000000..dbac65dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "2a025bb", + "full_sha": "2a025bb639796feb0e5d385a9a09ad8d37d1188d", + "parent": "990b3b6", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/diff.patch new file mode 100644 index 00000000..6156925a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index db727f1..f977472 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/provenance.json new file mode 100644 index 00000000..09460e3f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/commits/provenance.json @@ -0,0 +1,167 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 41, + "sha": "bb929c5", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 41, + "sha": "d76aa26", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "1b3b0ea", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "6a22990", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "abcf43f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 37, + "sha": "990b3b6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 41, + "sha": "2a025bb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/metadata.yaml new file mode 100644 index 00000000..1550c6f8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/metadata.yaml @@ -0,0 +1,159 @@ +benchmark: R3 +family: Architectural Reachability Drift +drift_name: IMDSv2 Disabled (SSRF to Instance Role) +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "A metadata-reachable debug helper is added to the instance (C5, Mild), then IMDSv2\ + \ enforcement is dropped (C8, Severe) \u2014 an SSRF in the app can read IMDSv1 and exfiltrate\ + \ the instance role credentials." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Instance metadata exposure: IMDSv2 enforced -> debug proxy added -> IMDSv1 allowed.' +commit_count: 11 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: 1b3b0ea + label: V2 + message: 'feat: add metadata debug helper to instance bootstrap' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: metadata_debug_helper + path: app -> metadata debug helper (IMDSv2 still enforced) + controls_present: + - Imdsv2Enforced + controls_absent: [] + component: WebServerInstance + transitive_reach: + - a metadata-adjacent debug surface + construct_ids: + - WebServerInstance + template_assertion: UserData gains a metadata proxy debug line + note: SSRF surface introduced, but IMDSv2 still blocks token-less reads. Mild. + C6: + sha: 6a22990 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: abcf43f + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C8: + sha: 990b3b6 + label: V3 + message: 'fix: allow imdsv1 for an older sdk on the instance' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: imdsv2_disabled + path: SSRF -> IMDSv1 -> WebServerRole credentials -> S3 + controls_present: [] + controls_removed: + - Imdsv2Enforced + component: WebServerInstance + transitive_reach: + - instance-role credentials via SSRF + construct_ids: + - WebServerInstance + template_assertion: LaunchTemplate MetadataOptions HttpTokens required -> optional + note: IMDSv1 allowed; an app SSRF yields the role's S3 read/write credentials. + C9: + sha: 2a025bb + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C10: + sha: bb929c5 + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C11: + sha: d76aa26 + label: VB + message: 'docs: add instance runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- WebServerRole has S3 read/write at C1 +- SSH 0.0.0.0/0 on the web server SG at C1 +scoring: + drift_commits: + - C5 + - C8 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C9 + - C10 + - C11 + false_positive_probes: + - C6 + - C9 + state_vs_event_probes: + - C10 + - C11 + severity_axis: instance metadata service exposure (IMDSv2 -> IMDSv1) + note: EC2-specific credential-theft axis; the Severe is a one-field MetadataOptions flip. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/repository new file mode 160000 index 00000000..d76aa26a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/repository @@ -0,0 +1 @@ +Subproject commit d76aa26a9f3a7ea317f976057e98a094b026c3c2 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/template_analysis.json new file mode 100644 index 00000000..0b5a073b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/R3/template_analysis.json @@ -0,0 +1,325 @@ +{ + "benchmark": "R3", + "drift_name": "IMDSv2 Disabled (SSRF to Instance Role)", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "1b3b0ea", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "6a22990", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "abcf43f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "990b3b6", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 37, + "template_diff_lines": 20, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "2a025bb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 41, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "bb929c5", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 41, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "d76aa26", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 41, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/README.md new file mode 100644 index 00000000..47732f14 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/README.md @@ -0,0 +1,127 @@ +# Drift Benchmark Suite — `simple-image-upload-download-website` + +Fifth suite in the family, alongside [`benchmarks/`](../benchmarks) (text-paste, 25), +[`benchmarks-ecs/`](../benchmarks-ecs) (30), [`benchmarks-support/`](../benchmarks-support) +(28) and [`benchmarks-chatbot/`](../benchmarks-chatbot) (32). **21 independent parallel +evolutions** of a classic image upload/download web app: a public Application Load Balancer +in front of an EC2 web server (private subnet, SSM-managed, IMDSv2, encrypted EBS), an S3 +image bucket (block-public-access, versioned, SSE, enforceSSL), a CloudFront distribution, +and an EC2 instance role with S3 read/write for presigned URLs. + +**194 commits · 194 CloudFormation templates · 31 MB · shared C1 = `0cac3cd`.** + +## Sized to the architecture + +This suite is deliberately **21 benchmarks, not a round number** — each family holds only +the drift mechanisms this stack genuinely supports, with no padding: + +| Family | Benchmarks | Why this count | +|--------|-----------|----------------| +| **R** — Reachability | R1–R3 (3) | the three real ways internal compute here becomes reachable | +| **G** — Privilege | G1–G4 (4) | the instance role's distinct escalation paths | +| **E** — Exposure | E1–E6 (6) | S3 is the data store, so it has several distinct controls, plus EBS and CloudFront | +| **D** — Dependency | D1–D4 (4) | the external services a real image site adds | +| **T** — Trust Boundary | T1–T4 (4) | the cross-account sharing an image site does | + +Each benchmark labels **exactly two commits** V2 (Mild) and V3 (Severe); the rest are +benign. C1 is byte-identical across all 21. + +## Drift roster + +- **R1** web-server SG bypasses the ALB · **R2** EC2 moved to a public subnet with SSH · **R3** IMDSv2 disabled (SSRF → instance-role credential theft) +- **G1** role S3 grant → wildcard · **G2** SSM session → full `ssm:*` (fleet RCE) · **G3** `iam:PassRole` escalation · **G4** PowerUser → Administrator +- **E1** S3 BlockPublicAccess off (**quintuple oracle**) · **E2** public S3 bucket policy · **E3** S3 encryption weakened (transit then at-rest) · **E4** S3 CORS `*` (image-upload-specific) · **E5** EBS encryption off · **E6** CloudFront viewer plaintext HTTP +- **D1** external image resizer · **D2** external CDN becomes origin · **D3** upload webhook signature off · **D4** malware scan fail-open +- **T1** image bucket cross-account share · **T2** cross-account role → Administrator · **T3** VPC peering · **T4** cross-account image replication + +## Commit-count distribution + +Non-linear; counts span **4 to 14** with no drift-position collision. + +| Count | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | +|---|---|---|---|---|---|---|---|---|---|---|---| +| Benchmarks | E1 G4 | G2 | E5 R2 | D1 E3 | G1 T1 | R1 T2 | D2 E2 | R3 T3 | D3 G3 | E4 T4 | D4 E6 | + +## Anti-correlation property + +**In all 21 benchmarks the Severe commit produces a smaller template diff than the biggest +benign commit** — the biggest benign diff is always cost-allocation tag propagation +(254–270 lines); severe drifts flip one or two fields. + +| Benchmark | Severe diff | Biggest benign | Ratio | +|---|---|---|---| +| T4 (replication → any bucket) | **2 lines** | 270 | **135:1** | +| T2 / T3 (cross-account role / peering) | **2 lines** | 268 | 134:1 | +| G4 (PowerUser → Administrator) | **2 lines** | 254 | 127:1 | +| D1–D4 · E4 · E5 · E6 | **2 lines** | 254 | 127:1 | +| E1 (BlockPublicAccess off) · G2 · G3 · R2 | 4 lines | 254 | 64:1 | + +A detector triaging by diff magnitude scores worse than chance. + +## Cross-suite scoring pairs + +| Pair | Both end with | Discriminator | +|---|---|---| +| paste G4 ↔ ecs G4 ↔ support G4 ↔ chatbot G4 ↔ **image G4 ↔ image T2** | `AdministratorAccess` | internal vs cross-account | +| paste E5 ↔ ecs E3 ↔ support E1 ↔ chatbot E1 ↔ **image E1** | S3 BlockPublicAccess off | **quintuple-baseline oracle** | +| support D3 ↔ chatbot D4 ↔ **image D3** | webhook signature flipped | image-moderation context | +| **image R3** | IMDSv2 → IMDSv1 | EC2 SSRF-to-credential axis unique to this baseline | +| **image E4** | S3 CORS `*` | browser-upload exposure unique to this baseline | + +## Per-benchmark layout + +Each `benchmarks-image//` mirrors the source dataset's `/cdk/` shape: + +``` +R1/ +├── repository/ real git repo + real .git +├── commits/ +│ ├── C1..CN/ +│ │ ├── cdk/ same layout as simple-image-upload-download-website/cdk/ +│ │ │ ├── cdk.ts, cdk.json, package.json, tsconfig.json +│ │ │ └── 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 +``` + +Resource counts run 37 (baseline) → 44. + +## Generators + +``` +python3 generators/build.py --suite benchmarks-image --all \ + --baseline /simple-image-upload-download-website/cdk --out-root benchmarks-image +python3 generators/synth.py --root benchmarks-image/R1 --workbench /tmp/wb \ + --context-json "$(cat benchmarks-image/synth_context.json)" +python3 generators/analyze.py --root benchmarks-image/R1 +python3 generators/docs.py --suite benchmarks-image --root-dir benchmarks-image +python3 generators/verify.py --suite benchmarks-image --all --root-dir benchmarks-image +``` + +Workbench: `npm install aws-cdk-lib@2.150.0 constructs@^10 ts-node typescript@5.6.3 @types/node`. + +## Baseline handling (recorded caveats) + +Two deterministic accommodations, documented in `generators/benchmarks-image/_image_common.py`: + +1. **No `package.json` / `tsconfig.json` at the cdk root** — supplied via `BASELINE_DEFAULTS`. +2. **Context injection** — the app reads `app.node.tryGetContext()`, so `synth.py` injects + it via `CDK_CONTEXT_JSON` (shipped as `synth_context.json`). A key pair is provided, so + the SSH-0.0.0.0/0 ingress exists at C1; encryption flags default true; logging flags + default false. No source normalization was needed (the baseline compiles under 2.150.0). + +The shared C1 SHA `0cac3cd` is deterministic from the source. + +## Validation performed + +- **Real git.** `git log/diff/bisect` all work. +- **Deterministic.** Same baseline → same SHAs. +- **Typechecked.** `cdk.ts` compiles under `tsc --noEmit` at every commit. +- **Synthesized.** All 194 commits produce valid CloudFormation (194 templates). +- **Verified.** `verify.py` asserts SHAs, messages and per-commit invariants — e.g. R3's + instance metadata options flip IMDSv2 required → optional; T2's `PartnerAccessRole` flips + `ReadOnlyAccess` → `AdministratorAccess` while its cross-account trust holds. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/README.md new file mode 100644 index 00000000..1dab1b63 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/README.md @@ -0,0 +1,147 @@ +# T1 — Image Bucket Cross-Account Share + +**Family:** Trust Boundary Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The image bucket policy grants read to a named foreign-account role (C3, Mild), then read/write/delete to the whole foreign account (C6, Severe). + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Image bucket cross-account grant: named foreign role read -> foreign account read/write. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `8797906` | V2 | **Mild** | Mild | 38 | **22** | +| C4 | `ce463fe` | VB | None | Mild | 38 | 254 | +| C5 | `a40739f` | VB | None | Mild | 38 | 2 | +| C6 | `a555d6c` | V3 | **Severe** | Severe | 38 | **19** | +| C7 | `fb34765` | VB | None | Severe | 42 | 139 | +| C8 | `896e800` | VB | None | Severe | 42 | **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 254 lines**. +The smallest drift-bearing diff is **C6 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: allow partner role to read published images` + +- **Mechanism:** `s3_cross_account_named_role` +- **Path:** `222233334444 PartnerImageReader -> s3:GetObject -> ImageStorageBucket/*` +- **Controls present:** SingleForeignRole, ReadOnly +- **Component:** `ImageStorageBucket` +- **Transitive reach:** one foreign role reads images +- **Template assertion:** BucketPolicy with ArnPrincipal role/PartnerImageReader on s3:GetObject + +Cross-account read, scoped to one role. Mild. + +### C6 — V3, Severe + +`fix: give partner account full image access for a joint sync` + +- **Mechanism:** `s3_cross_account_full` +- **Path:** `whole account 222233334444 -> read/write/delete -> ImageStorageBucket/*` +- **Controls present:** none +- **Controls removed:** SingleForeignRole, ReadOnly +- **Component:** `ImageStorageBucket` +- **Transitive reach:** a foreign account can read, overwrite and delete images +- **Template assertion:** Principal becomes the foreign account root; actions gain Put/Delete + +Trust widens from one role to a whole foreign account, with mutation rights. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: extend image retention to two years` — retention only +- **C7** `chore: enable vpc flow logs` — security improvement decoy +- **C8** `docs: add data sharing runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- BlockPublicAccess on at C1 (cross-account still allowed by policy) +- images are user-uploaded content at C1 + +## Scoring + +- **Drift commits:** C3, C6 +- **Benign commits:** C2, C4, C5, C7, C8 +- **False-positive probes:** C4, C7 + +- **State-vs-event probes:** C8 + +Cross-account data sharing; named foreign role read -> whole account read/write. + +## 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/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..610d6145 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, + principals: [new iam.ArnPrincipal('arn:aws:iam::222233334444:role/PartnerImageReader')], + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/commit.json new file mode 100644 index 00000000..7c867a86 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "8797906", + "full_sha": "8797906e21850caeae714b88a97a254d5855c117", + "parent": "d4e7e9b", + "message": "feat: allow partner role to read published images", + "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/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/diff.patch new file mode 100644 index 00000000..533d6a71 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C3/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..610d614 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -128,6 +128,14 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }) + ); + ++ this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'PartnerCrossAccount', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.ArnPrincipal('arn:aws:iam::222233334444:role/PartnerImageReader')], ++ actions: ['s3:GetObject'], ++ resources: [`${this.imageBucket.bucketArn}/*`], ++ })); ++ + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..be405705 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, + principals: [new iam.ArnPrincipal('arn:aws:iam::222233334444:role/PartnerImageReader')], + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/commit.json new file mode 100644 index 00000000..29788076 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "ce463fe", + "full_sha": "ce463fe10a8747bb088cf2c310116fe223b66a66", + "parent": "8797906", + "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/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/diff.patch new file mode 100644 index 00000000..a80e5f59 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 610d614..be40570 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..571249a2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, + principals: [new iam.ArnPrincipal('arn:aws:iam::222233334444:role/PartnerImageReader')], + actions: ['s3:GetObject'], + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/commit.json new file mode 100644 index 00000000..2afed1a9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "a40739f", + "full_sha": "a40739f92ae7b7a6a144d0955d8028e22f820086", + "parent": "ce463fe", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/diff.patch new file mode 100644 index 00000000..f06c7727 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index be40570..571249a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -116,7 +116,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..04438264 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('222233334444')], + actions: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject'], // full object access + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/commit.json new file mode 100644 index 00000000..8ceb91b8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "a555d6c", + "full_sha": "a555d6c4d00052b46760824c7879539fda0e1874", + "parent": "a40739f", + "message": "fix: give partner account full image access for a joint sync", + "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/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/diff.patch new file mode 100644 index 00000000..293daa87 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C6/diff.patch @@ -0,0 +1,14 @@ +diff --git a/cdk.ts b/cdk.ts +index 571249a..0443826 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -136,8 +136,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, +- principals: [new iam.ArnPrincipal('arn:aws:iam::222233334444:role/PartnerImageReader')], +- actions: ['s3:GetObject'], ++ principals: [new iam.AccountPrincipal('222233334444')], ++ actions: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject'], // full object access + resources: [`${this.imageBucket.bucketArn}/*`], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..b781ef8a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('222233334444')], + actions: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject'], // full object access + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/commit.json new file mode 100644 index 00000000..ea9d046a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "fb34765", + "full_sha": "fb3476551da04bd251bd4f5f77a4956d93e50219", + "parent": "a555d6c", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/diff.patch new file mode 100644 index 00000000..b54fa138 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 0443826..b781ef8 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..b781ef8a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + this.imageBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerCrossAccount', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('222233334444')], + actions: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject'], // full object access + resources: [`${this.imageBucket.bucketArn}/*`], + })); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/commit.json new file mode 100644 index 00000000..9b98f200 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "896e800", + "full_sha": "896e800c9955b03dc525b8e6a020dfd8a3726a91", + "parent": "fb34765", + "message": "docs: add data sharing 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/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/provenance.json new file mode 100644 index 00000000..037beee7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/commits/provenance.json @@ -0,0 +1,122 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8797906", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "ce463fe", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "a40739f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "a555d6c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "fb34765", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 42, + "sha": "896e800", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/metadata.yaml new file mode 100644 index 00000000..90fe01fd --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/metadata.yaml @@ -0,0 +1,132 @@ +benchmark: T1 +family: Trust Boundary Drift +drift_name: Image Bucket Cross-Account Share +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: The image bucket policy grants read to a named foreign-account role (C3, Mild), then + read/write/delete to the whole foreign account (C6, Severe). +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Image bucket cross-account grant: named foreign role read -> foreign account read/write.' +commit_count: 8 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: '8797906' + label: V2 + message: 'feat: allow partner role to read published images' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: s3_cross_account_named_role + path: 222233334444 PartnerImageReader -> s3:GetObject -> ImageStorageBucket/* + controls_present: + - SingleForeignRole + - ReadOnly + controls_absent: [] + component: ImageStorageBucket + transitive_reach: + - one foreign role reads images + construct_ids: + - ImageStorageBucket + template_assertion: BucketPolicy with ArnPrincipal role/PartnerImageReader on s3:GetObject + note: Cross-account read, scoped to one role. Mild. + C4: + sha: ce463fe + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: a40739f + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: retention only + C6: + sha: a555d6c + label: V3 + message: 'fix: give partner account full image access for a joint sync' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: s3_cross_account_full + path: whole account 222233334444 -> read/write/delete -> ImageStorageBucket/* + controls_present: [] + controls_removed: + - SingleForeignRole + - ReadOnly + component: ImageStorageBucket + transitive_reach: + - a foreign account can read, overwrite and delete images + construct_ids: + - ImageStorageBucket + template_assertion: Principal becomes the foreign account root; actions gain Put/Delete + note: Trust widens from one role to a whole foreign account, with mutation rights. + C7: + sha: fb34765 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C8: + sha: 896e800 + label: VB + message: 'docs: add data sharing runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- BlockPublicAccess on at C1 (cross-account still allowed by policy) +- images are user-uploaded content at C1 +scoring: + drift_commits: + - C3 + - C6 + benign_commits: + - C2 + - C4 + - C5 + - C7 + - C8 + false_positive_probes: + - C4 + - C7 + state_vs_event_probes: + - C8 + severity_axis: image bucket cross-account principal + action breadth + note: Cross-account data sharing; named foreign role read -> whole account read/write. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/repository new file mode 160000 index 00000000..896e800c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/repository @@ -0,0 +1 @@ +Subproject commit 896e800c9955b03dc525b8e6a020dfd8a3726a91 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/template_analysis.json new file mode 100644 index 00000000..5352511e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T1/template_analysis.json @@ -0,0 +1,238 @@ +{ + "benchmark": "T1", + "drift_name": "Image Bucket Cross-Account Share", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "8797906", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 22, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "ce463fe", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 254, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "a40739f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "a555d6c", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 38, + "template_diff_lines": 19, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "fb34765", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "896e800", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 42, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/README.md new file mode 100644 index 00000000..1f9911b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/README.md @@ -0,0 +1,149 @@ +# T2 — Cross-Account Role to Administrator + +**Family:** Trust Boundary Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +A role trusted by a partner account is created with ReadOnlyAccess (C5, Mild), then escalated to AdministratorAccess (C8, Severe) while the cross-account trust stays. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Cross-account role managed policy: ReadOnly -> 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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `c78f86d` | VB | None | None | 38 | 2 | +| C5 | `7a2a43e` | V2 | **Mild** | Mild | 39 | **42** | +| C6 | `4a6262a` | VB | None | Mild | 39 | 268 | +| C7 | `e3da408` | VB | None | Mild | 43 | 139 | +| C8 | `64ac6ac` | V3 | **Severe** | Severe | 43 | **2** | +| C9 | `cd5e27d` | VB | None | Severe | 43 | **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 268 lines**. +The smallest drift-bearing diff is **C8 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: add partner cross-account read-only role` + +- **Mechanism:** `cross_account_role_readonly` +- **Path:** `AWS 222233334444 -> AssumeRole -> PartnerAccessRole (ReadOnly)` +- **Controls present:** ReadOnlyPolicy, SingleNamedAccount +- **Component:** `PartnerAccessRole` +- **Transitive reach:** read-only across the account +- **Template assertion:** Role trusts 222233334444, ManagedPolicy ReadOnlyAccess + +Cross-account trust at read-only. Mild. + +### C8 — V3, Severe + +`fix: grant partner role admin for the joint migration` + +- **Mechanism:** `cross_account_role_admin` +- **Path:** `AWS 222233334444 -> AssumeRole -> PartnerAccessRole (Administrator)` +- **Controls present:** SingleNamedAccount +- **Controls removed:** ReadOnlyPolicy +- **Component:** `PartnerAccessRole` +- **Transitive reach:** full admin from a foreign account +- **Template assertion:** ManagedPolicyArns ReadOnlyAccess -> AdministratorAccess; trust unchanged + +Same managed-policy flip as G4, but across the account boundary. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: widen cloudfront price class` — edge coverage only +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: enable vpc flow logs` — security improvement decoy +- **C9** `docs: add cross-account access runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- no permissions boundary on cross-account roles at C1 + +## Scoring + +- **Drift commits:** C5, C8 +- **Benign commits:** C2, C3, C4, C6, C7, C9 +- **False-positive probes:** C6, C7 + +- **State-vs-event probes:** C9 + +Cross-suite AdministratorAccess pair: internal (G4) vs cross-account (T2). + +## 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/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..14b1d1e0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/commit.json new file mode 100644 index 00000000..d016b363 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "c78f86d", + "full_sha": "c78f86d24f20e3a3a7219d8bda3575c5f43e6489", + "parent": "10163b2", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/diff.patch new file mode 100644 index 00000000..21bc2b11 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C4/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..14b1d1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -256,7 +256,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..f7799f85 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/cdk.ts @@ -0,0 +1,335 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('222233334444'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/commit.json new file mode 100644 index 00000000..b96e500b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "7a2a43e", + "full_sha": "7a2a43ec097324dbb6035f3da3522ee7d635be42", + "parent": "c78f86d", + "message": "feat: add partner cross-account read-only role", + "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 | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/diff.patch new file mode 100644 index 00000000..d83f3c24 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 14b1d1e..f7799f8 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,6 +260,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + ++ const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { ++ assumedBy: new iam.AccountPrincipal('222233334444'), ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], ++ }); ++ + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..58ce29bb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/cdk.ts @@ -0,0 +1,340 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('222233334444'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/commit.json new file mode 100644 index 00000000..7b727e9f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "4a6262a", + "full_sha": "4a6262a357ce8388e01dd90f15f27957ba849db9", + "parent": "7a2a43e", + "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/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/diff.patch new file mode 100644 index 00000000..dbddd08a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index f7799f8..58ce29b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..9495c5a0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('222233334444'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/commit.json new file mode 100644 index 00000000..d6af8ea8 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "e3da408", + "full_sha": "e3da408ba33fa7e976854671e52eed4768e43010", + "parent": "4a6262a", + "message": "chore: enable vpc flow logs", + "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, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/diff.patch new file mode 100644 index 00000000..cafc36c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 58ce29b..9495c5a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..55b42f20 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('222233334444'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/commit.json new file mode 100644 index 00000000..c3733e2f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "64ac6ac", + "full_sha": "64ac6acd7bb620674727a91b30d4c7e3a9703cc8", + "parent": "e3da408", + "message": "fix: grant partner role admin for the joint migration", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/diff.patch new file mode 100644 index 00000000..e915850b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 9495c5a..55b42f2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -269,7 +269,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + + const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('222233334444'), +- managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // --- Outputs --- \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..55b42f20 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/cdk.ts @@ -0,0 +1,342 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerAccessRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('222233334444'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/commit.json new file mode 100644 index 00000000..bcda916b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "cd5e27d", + "full_sha": "cd5e27d0b2600a6b2cb2dac340e843f1a552bc4e", + "parent": "64ac6ac", + "message": "docs: add cross-account access 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/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/provenance.json new file mode 100644 index 00000000..a2007531 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "c78f86d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 39, + "sha": "7a2a43e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 39, + "sha": "4a6262a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 43, + "sha": "e3da408", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 43, + "sha": "64ac6ac", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 43, + "sha": "cd5e27d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/metadata.yaml new file mode 100644 index 00000000..041524c6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/metadata.yaml @@ -0,0 +1,140 @@ +benchmark: T2 +family: Trust Boundary Drift +drift_name: Cross-Account Role to Administrator +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: A role trusted by a partner account is created with ReadOnlyAccess (C5, Mild), then + escalated to AdministratorAccess (C8, Severe) while the cross-account trust stays. +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Cross-account role managed policy: ReadOnly -> Administrator.' +commit_count: 9 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: c78f86d + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: edge coverage only + C5: + sha: 7a2a43e + label: V2 + message: 'feat: add partner cross-account read-only role' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_account_role_readonly + path: AWS 222233334444 -> AssumeRole -> PartnerAccessRole (ReadOnly) + controls_present: + - ReadOnlyPolicy + - SingleNamedAccount + controls_absent: [] + component: PartnerAccessRole + transitive_reach: + - read-only across the account + construct_ids: + - PartnerAccessRole + template_assertion: Role trusts 222233334444, ManagedPolicy ReadOnlyAccess + note: Cross-account trust at read-only. Mild. + C6: + sha: 4a6262a + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: e3da408 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C8: + sha: 64ac6ac + label: V3 + message: 'fix: grant partner role admin for the joint migration' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: cross_account_role_admin + path: AWS 222233334444 -> AssumeRole -> PartnerAccessRole (Administrator) + controls_present: + - SingleNamedAccount + controls_removed: + - ReadOnlyPolicy + component: PartnerAccessRole + transitive_reach: + - full admin from a foreign account + construct_ids: + - PartnerAccessRole + template_assertion: ManagedPolicyArns ReadOnlyAccess -> AdministratorAccess; trust unchanged + note: Same managed-policy flip as G4, but across the account boundary. + C9: + sha: cd5e27d + label: VB + message: 'docs: add cross-account access runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- no permissions boundary on cross-account roles at C1 +scoring: + drift_commits: + - C5 + - C8 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C9 + false_positive_probes: + - C6 + - C7 + state_vs_event_probes: + - C9 + severity_axis: cross-account role managed-policy breadth + note: 'Cross-suite AdministratorAccess pair: internal (G4) vs cross-account (T2).' diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/repository new file mode 160000 index 00000000..cd5e27d0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/repository @@ -0,0 +1 @@ +Subproject commit cd5e27d0b2600a6b2cb2dac340e843f1a552bc4e diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/template_analysis.json new file mode 100644 index 00000000..f1eca81b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T2/template_analysis.json @@ -0,0 +1,272 @@ +{ + "benchmark": "T2", + "drift_name": "Cross-Account Role to Administrator", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "c78f86d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "7a2a43e", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 39, + "template_diff_lines": 42, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "4a6262a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 39, + "template_diff_lines": 268, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "e3da408", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 43, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "64ac6ac", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 43, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "cd5e27d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 43, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/README.md new file mode 100644 index 00000000..e9424566 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/README.md @@ -0,0 +1,156 @@ +# T3 — VPC Peering to Partner Account + +**Family:** Trust Boundary Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +The app VPC peers to a partner VPC in another account with a scoped route (C4, Mild), then the route is widened to 0.0.0.0/0 (C9, Severe). + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Cross-account peering route: named CIDR -> 0.0.0.0/0. + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `75315fb` | V2 | **Mild** | Mild | 40 | **22** | +| C5 | `4524a52` | VB | None | Mild | 40 | 268 | +| C6 | `74d491a` | VB | None | Mild | 40 | 2 | +| C7 | `e738901` | VB | None | Mild | 40 | 2 | +| C8 | `1487224` | VB | None | Mild | 40 | 2 | +| C9 | `b2ad944` | V3 | **Severe** | Severe | 40 | **2** | +| C10 | `6a672f4` | VB | None | Severe | 44 | 139 | +| C11 | `29f08b1` | VB | None | Severe | 44 | **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 268 lines**. +The smallest drift-bearing diff is **C9 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: peer app vpc to the partner vpc` + +- **Mechanism:** `vpc_peering_scoped` +- **Path:** `app VPC <-> partner VPC (222233334444), scoped route` +- **Controls present:** ScopedPeerRoute +- **Component:** `AppVpc` +- **Transitive reach:** the partner CIDR +- **Template assertion:** VPCPeeringConnection + Route to 10.9.0.0/16 + +Cross-account peering with a narrow route. Mild. + +### C9 — V3, Severe + +`fix: widen peer route to cover all partner subnets` + +- **Mechanism:** `vpc_peering_route_wide` +- **Path:** `app VPC -> 0.0.0.0/0 -> peer account` +- **Controls present:** none +- **Controls removed:** ScopedPeerRoute +- **Component:** `AppVpc` +- **Transitive reach:** any destination across the peering +- **Template assertion:** peer Route destination becomes 0.0.0.0/0 + +The trust boundary is effectively erased for routed traffic. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: raise healthy threshold count` — target group only +- **C7** `chore: widen cloudfront price class` — edge coverage only +- **C8** `chore: grow root volume to 50gb` — block device size only +- **C10** `chore: enable vpc flow logs` — security improvement decoy +- **C11** `docs: add networking runbook` — no template change while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- single NAT gateway at C1 +- no network ACLs beyond SGs at C1 + +## Scoring + +- **Drift commits:** C4, C9 +- **Benign commits:** C2, C3, C5, C6, C7, C8, C10, C11 +- **False-positive probes:** C5, C10 + +- **State-vs-event probes:** C11 + +Network trust boundary; mirrors peering drift across the suites. + +## 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/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..4d6d5d22 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/cdk.ts @@ -0,0 +1,348 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '0.0.0.0/0', // all traffic to peer + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/commit.json new file mode 100644 index 00000000..e17be63b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "6a672f4", + "full_sha": "6a672f4a806907c91b5ab18453c510dd7fc58e36", + "parent": "b2ad944", + "message": "chore: enable vpc flow logs", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/diff.patch new file mode 100644 index 00000000..987e96d0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 163ff96..4d6d5d2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..4d6d5d22 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/cdk.ts @@ -0,0 +1,348 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '0.0.0.0/0', // all traffic to peer + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/commit.json new file mode 100644 index 00000000..866e96f1 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "29f08b1", + "full_sha": "29f08b19f1c786bfde78ff4a227c91efd6d070c9", + "parent": "6a672f4", + "message": "docs: add networking runbook", + "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 | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..202ef8a5 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/cdk.ts @@ -0,0 +1,341 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '10.9.0.0/16', // partner cidr + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/commit.json new file mode 100644 index 00000000..35bf574c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "75315fb", + "full_sha": "75315fb5048cfc13ebd8d2a3ab68be26183c6c62", + "parent": "10163b2", + "message": "feat: peer app vpc to the partner vpc", + "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 | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/diff.patch new file mode 100644 index 00000000..13dbf8f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C4/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..202ef8a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,6 +260,17 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + ++ const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { ++ vpcId: vpc.vpcId, ++ peerVpcId: 'vpc-0partner000000', ++ peerOwnerId: '222233334444', ++ }); ++ new ec2.CfnRoute(this, 'PartnerPeerRoute', { ++ routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, ++ destinationCidrBlock: '10.9.0.0/16', // partner cidr ++ vpcPeeringConnectionId: partnerPeering.ref, ++ }); ++ + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..c8ce6318 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '10.9.0.0/16', // partner cidr + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/commit.json new file mode 100644 index 00000000..aac771c2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "4524a52", + "full_sha": "4524a52b0888e581a8c40d006faec112c15d61ba", + "parent": "75315fb", + "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/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/diff.patch new file mode 100644 index 00000000..62a2f585 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 202ef8a..c8ce631 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..9087dd3e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '10.9.0.0/16', // partner cidr + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/commit.json new file mode 100644 index 00000000..a5fc537c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "74d491a", + "full_sha": "74d491a5fc1b171ab2c9daa11d7f52fefbd7d594", + "parent": "4524a52", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/diff.patch new file mode 100644 index 00000000..27ff5e8f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index c8ce631..9087dd3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -218,7 +218,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..ed95d8d2 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '10.9.0.0/16', // partner cidr + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/commit.json new file mode 100644 index 00000000..38e89812 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "e738901", + "full_sha": "e738901026098014a14b3512ebfbc0e1e926f254", + "parent": "74d491a", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/diff.patch new file mode 100644 index 00000000..5641ccc6 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C7/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 9087dd3..ed95d8d 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -261,7 +261,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..b56531f9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '10.9.0.0/16', // partner cidr + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/commit.json new file mode 100644 index 00000000..1de5c32b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "1487224", + "full_sha": "14872240d341ff280431d688959af714addab10c", + "parent": "e738901", + "message": "chore: grow root volume to 50gb", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/diff.patch new file mode 100644 index 00000000..09df66a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index ed95d8d..b56531f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -154,7 +154,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..163ff963 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const partnerPeering = new ec2.CfnVPCPeeringConnection(this, 'PartnerPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-0partner000000', + peerOwnerId: '222233334444', + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, + destinationCidrBlock: '0.0.0.0/0', // all traffic to peer + vpcPeeringConnectionId: partnerPeering.ref, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/commit.json new file mode 100644 index 00000000..328ff38f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "b2ad944", + "full_sha": "b2ad944f98aaa2de1558d24e4b43838f1d72a8ac", + "parent": "1487224", + "message": "fix: widen peer route to cover all partner subnets", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/diff.patch new file mode 100644 index 00000000..75bbe299 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index b56531f..163ff96 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -272,7 +272,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + new ec2.CfnRoute(this, 'PartnerPeerRoute', { + routeTableId: vpc.privateSubnets[0].routeTable.routeTableId, +- destinationCidrBlock: '10.9.0.0/16', // partner cidr ++ destinationCidrBlock: '0.0.0.0/0', // all traffic to peer + vpcPeeringConnectionId: partnerPeering.ref, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/provenance.json new file mode 100644 index 00000000..d3345b28 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/commits/provenance.json @@ -0,0 +1,167 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "6a672f4", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "29f08b1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "75315fb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "4524a52", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "74d491a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "e738901", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "1487224", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "b2ad944", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/metadata.yaml new file mode 100644 index 00000000..5b6b7d32 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/metadata.yaml @@ -0,0 +1,158 @@ +benchmark: T3 +family: Trust Boundary Drift +drift_name: VPC Peering to Partner Account +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: The app VPC peers to a partner VPC in another account with a scoped route (C4, Mild), + then the route is widened to 0.0.0.0/0 (C9, Severe). +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Cross-account peering route: named CIDR -> 0.0.0.0/0.' +commit_count: 11 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 75315fb + label: V2 + message: 'feat: peer app vpc to the partner vpc' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: vpc_peering_scoped + path: app VPC <-> partner VPC (222233334444), scoped route + controls_present: + - ScopedPeerRoute + controls_absent: [] + component: AppVpc + transitive_reach: + - the partner CIDR + construct_ids: + - PartnerPeering + - PartnerPeerRoute + template_assertion: VPCPeeringConnection + Route to 10.9.0.0/16 + note: Cross-account peering with a narrow route. Mild. + C5: + sha: 4524a52 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 74d491a + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: target group only + C7: + sha: e738901 + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: edge coverage only + C8: + sha: '1487224' + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: block device size only + C9: + sha: b2ad944 + label: V3 + message: 'fix: widen peer route to cover all partner subnets' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: vpc_peering_route_wide + path: app VPC -> 0.0.0.0/0 -> peer account + controls_present: [] + controls_removed: + - ScopedPeerRoute + component: AppVpc + transitive_reach: + - any destination across the peering + construct_ids: + - PartnerPeerRoute + template_assertion: peer Route destination becomes 0.0.0.0/0 + note: The trust boundary is effectively erased for routed traffic. + C10: + sha: 6a672f4 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: security improvement decoy + C11: + sha: 29f08b1 + label: VB + message: 'docs: add networking runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- single NAT gateway at C1 +- no network ACLs beyond SGs at C1 +scoring: + drift_commits: + - C4 + - C9 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C7 + - C8 + - C10 + - C11 + false_positive_probes: + - C5 + - C10 + state_vs_event_probes: + - C11 + severity_axis: cross-account peering route breadth + note: Network trust boundary; mirrors peering drift across the suites. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/repository new file mode 160000 index 00000000..29f08b19 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/repository @@ -0,0 +1 @@ +Subproject commit 29f08b19f1c786bfde78ff4a227c91efd6d070c9 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/template_analysis.json new file mode 100644 index 00000000..790053a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T3/template_analysis.json @@ -0,0 +1,325 @@ +{ + "benchmark": "T3", + "drift_name": "VPC Peering to Partner Account", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "75315fb", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 22, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "4524a52", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 268, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "74d491a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "e738901", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "1487224", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "b2ad944", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 40, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "6a672f4", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 44, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "29f08b1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 44, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/README.md b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/README.md new file mode 100644 index 00000000..76c09381 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/README.md @@ -0,0 +1,162 @@ +# T4 — Cross-Account Image Replication + +**Family:** Trust Boundary Drift +**Baseline:** `simple-image-upload-download-website` · **Stack:** `SimpleImageWebAppStack-development` + +A replication role targets a named foreign-account bucket (C7, Mild), then any bucket (C11, Severe) — user images are copied across the account boundary. + +> Every benchmark in this suite is an independent parallel evolution of the same simple-image-upload-download-website baseline 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 the baseline could have evolved. + +## Severity axis + +Replication destination: named foreign bucket -> any bucket ('*'). + +- `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 | `0cac3cd` | V1 | None | None | 38 | — | +| C2 | `d4e7e9b` | VB | None | None | 38 | 2 | +| C3 | `10163b2` | VB | None | None | 38 | 2 | +| C4 | `8101537` | VB | None | None | 38 | 2 | +| C5 | `43cabbb` | VB | None | None | 38 | 2 | +| C6 | `ded0767` | VB | None | None | 38 | 2 | +| C7 | `1fa5f97` | V2 | **Mild** | Mild | 40 | **41** | +| C8 | `8f9b19a` | VB | None | Mild | 40 | 270 | +| C9 | `5756e71` | VB | None | Mild | 44 | 139 | +| C10 | `4542377` | VB | None | Mild | 44 | 5 | +| C11 | `9adaf6c` | V3 | **Severe** | Severe | 44 | **2** | +| C12 | `0cfdf00` | VB | None | Severe | 44 | **0** | +| C13 | `35f0a9e` | VB | None | Severe | 44 | **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 **C8 at 270 lines**. +The smallest drift-bearing diff is **C11 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 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 0 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C7 — V2, Mild + +`feat: replicate images to the partner mirror bucket` + +- **Mechanism:** `s3_replication_named` +- **Path:** `ImageReplicationRole -> s3:ReplicateObject -> partner-mirror-222233334444` +- **Controls present:** SingleForeignBucket +- **Component:** `ImageReplicationRole` +- **Transitive reach:** images copied to one foreign bucket +- **Template assertion:** policy s3:ReplicateObject Resource = a named foreign bucket ARN + +Cross-account replication, scoped to one destination. Mild. + +### C11 — V3, Severe + +`fix: widen replication destination for multi-region mirrors` + +- **Mechanism:** `s3_replication_any` +- **Path:** `ImageReplicationRole -> s3:ReplicateObject -> * (any bucket)` +- **Controls present:** none +- **Controls removed:** SingleForeignBucket +- **Component:** `ImageReplicationRole` +- **Transitive reach:** images replicable to any bucket in any account +- **Template assertion:** ReplicateObject Resource becomes arn:aws:s3:::* + +User images can leave to an arbitrary destination across the account boundary. + + +## Benign commits — why each is a probe + +- **C2** `chore: tighten alb health check interval` — target group only +- **C3** `chore: extend image retention to two years` — retention only +- **C4** `chore: raise healthy threshold count` — target group only +- **C5** `chore: widen cloudfront price class` — edge coverage only +- **C6** `chore: grow root volume to 50gb` — block device size only +- **C8** `chore: apply cost-allocation tags` — largest template diff +- **C9** `chore: enable vpc flow logs` — security improvement decoy +- **C10** `chore: add vpc id stack output` — cosmetic output +- **C12** `docs: add replication runbook` — no template change while cumulative Severe +- **C13** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- images are user content at C1 +- no egress destination allowlist at C1 + +## Scoring + +- **Drift commits:** C7, C11 +- **Benign commits:** C2, C3, C4, C5, C6, C8, C9, C10, C12, C13 +- **False-positive probes:** C8, C9 + +- **State-vs-event probes:** C12, C13 + +Cross-account data-egress axis; images replicated off-account, then anywhere. + +## 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, 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/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..64e9905e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(30), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/commit.json new file mode 100644 index 00000000..2a14194c --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "0cac3cd", + "full_sha": "0cac3cd17432116e0085d4051f8fbb370b860c3e", + "parent": null, + "message": "feat: simple image upload/download website 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 | 69 ++++++++++++\n cdk.ts | 330 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 438 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..e76d8c3d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::partner-mirror-222233334444/*'], // named foreign bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/commit.json new file mode 100644 index 00000000..a303345e --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "4542377", + "full_sha": "4542377c7190804fe4a6df26a658c79a83425788", + "parent": "5756e71", + "message": "chore: add vpc id stack output", + "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": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/diff.patch new file mode 100644 index 00000000..d27c2d8d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 36ffe95..e76d8c3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -276,6 +276,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + })); + + // --- Outputs --- ++ new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..cef7652d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::*'], // any destination bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/commit.json new file mode 100644 index 00000000..502cabd0 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "9adaf6c", + "full_sha": "9adaf6c940f2a2416157be81198f1dc1e6b1707d", + "parent": "4542377", + "message": "fix: widen replication destination for multi-region mirrors", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-05-13T10: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/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/diff.patch new file mode 100644 index 00000000..decacd50 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index e76d8c3..cef7652 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -272,7 +272,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], +- resources: ['arn:aws:s3:::partner-mirror-222233334444/*'], // named foreign bucket ++ resources: ['arn:aws:s3:::*'], // any destination bucket + })); + + // --- Outputs --- \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..cef7652d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::*'], // any destination bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/commit.json new file mode 100644 index 00000000..539e9e12 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "0cfdf00", + "full_sha": "0cfdf00a2070a691ae687ac1f5ca89a47d5a9454", + "parent": "9adaf6c", + "message": "docs: add replication 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/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/diff.patch new file mode 100644 index 00000000..e9a09609 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 1170442..3e4ecaf 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Classic image web app: a public Application Load Balancer in front of an EC2 web + (private subnet, SSM-managed, IMDSv2), an S3 image bucket (block-public-access on, + versioned, encrypted), and a CloudFront distribution. The EC2 instance role has S3 + read/write for presigned upload/download URLs. ++ ++## Runbook ++ ++- Images live in the S3 bucket (versioned); the EC2 role issues presigned URLs. ++- CloudFront fronts the ALB; the web server sits in a private subnet. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..cef7652d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/cdk.ts @@ -0,0 +1,346 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::*'], // any destination bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'VpcIdOutput', { value: vpc.vpcId }); + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/commit.json new file mode 100644 index 00000000..a20df945 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "35f0a9e", + "full_sha": "35f0a9e79002fef9e9284a213f873a24d2f1eeb7", + "parent": "0cfdf00", + "message": "chore: extend gitignore", + "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": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C13/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..d1004921 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/commit.json new file mode 100644 index 00000000..053e0b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "d4e7e9b", + "full_sha": "d4e7e9bd58cd101505344d31fa4bc53094815a28", + "parent": "0cac3cd", + "message": "chore: tighten alb health check interval", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/diff.patch new file mode 100644 index 00000000..bc3f5833 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e9905..d100492 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application +- interval: cdk.Duration.seconds(30), ++ interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a697e860 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 2, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/commit.json new file mode 100644 index 00000000..6f1646c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "10163b2", + "full_sha": "10163b24e2a22786ee4c30ae42a626c88402a507", + "parent": "d4e7e9b", + "message": "chore: extend image retention to two years", + "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 | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/diff.patch new file mode 100644 index 00000000..8995d0f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index d100492..a697e86 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -111,7 +111,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects older than 1 year ++ expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a040092b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/commit.json new file mode 100644 index 00000000..d75cf97a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "8101537", + "full_sha": "81015375e2bfde2abb354cd48ba515b0bd79d74b", + "parent": "10163b2", + "message": "chore: raise healthy threshold count", + "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/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/diff.patch new file mode 100644 index 00000000..6279316f --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a697e86..a040092 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,7 +213,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), +- healthyThresholdCount: 2, ++ healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..52a31153 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/commit.json new file mode 100644 index 00000000..e85d8e9a --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "43cabbb", + "full_sha": "43cabbb760ea90783e1d82b748fa13c3d6350e33", + "parent": "8101537", + "message": "chore: widen cloudfront price class", + "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/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/diff.patch new file mode 100644 index 00000000..51632666 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a040092..52a3115 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -256,7 +256,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', +- priceClass: cloudfront.PriceClass.PRICE_CLASS_100, // Choose appropriate price class ++ priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..22a06bfd --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/cdk.ts @@ -0,0 +1,330 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_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/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/commit.json new file mode 100644 index 00000000..00f92113 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "ded0767", + "full_sha": "ded0767110c388f3caac270a983a232466267b9d", + "parent": "43cabbb", + "message": "chore: grow root volume to 50gb", + "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": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/diff.patch new file mode 100644 index 00000000..7064913d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 52a3115..22a06bf 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -149,7 +149,7 @@ export class SimpleImageWebAppStack extends cdk.Stack { + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume +- volume: ec2.BlockDeviceVolume.ebs(30, { // 30 GB EBS volume ++ volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..a6e8cf80 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/cdk.ts @@ -0,0 +1,338 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::partner-mirror-222233334444/*'], // named foreign bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/commit.json new file mode 100644 index 00000000..b5916a84 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "1fa5f97", + "full_sha": "1fa5f97f4ed7efd5b16a9c0ce27a7e6556b0dc26", + "parent": "ded0767", + "message": "feat: replicate images to the partner mirror bucket", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-15T10: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/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/diff.patch new file mode 100644 index 00000000..219fe165 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C7/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 22a06bf..a6e8cf8 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -260,6 +260,14 @@ export class SimpleImageWebAppStack extends cdk.Stack { + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + ++ const replicationRole = new iam.Role(this, 'ImageReplicationRole', { ++ assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), ++ }); ++ replicationRole.addToPolicy(new iam.PolicyStatement({ ++ actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], ++ resources: ['arn:aws:s3:::partner-mirror-222233334444/*'], // named foreign bucket ++ })); ++ + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..738b7a27 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/cdk.ts @@ -0,0 +1,343 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::partner-mirror-222233334444/*'], // named foreign bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/commit.json new file mode 100644 index 00000000..90e753b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "8f9b19a", + "full_sha": "8f9b19a379b21cdf35c197e79e2a304e37b025d1", + "parent": "1fa5f97", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-22T10: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/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/diff.patch new file mode 100644 index 00000000..afb3aacb --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C8/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index a6e8cf8..738b7a2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -64,6 +64,11 @@ export class SimpleImageWebAppStack extends cdk.Stack { + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-3310'); ++ cdk.Tags.of(this).add('Owner', 'web-platform'); ++ cdk.Tags.of(this).add('DataClass', 'user-images'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..704cbf7b --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.json @@ -0,0 +1,69 @@ +{ + "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:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/aws-ecs:overrideHealthCheckGracePeriod": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-efs:defaultEncryptionAtRest": true, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, + "vpcId": null, + "availabilityZones": ["us-east-1a", "us-east-1b"], + "region": "us-east-1", + "accountId": "123456789012", + "logRetentionDays": 30, + "applicationName": "SimpleImageWebApp", + "environmentType": "development", + "ec2InstanceType": "t3.micro", + "ec2KeyPairName": "your-key-pair-name", + "enableTransitEncryption": true, + "enableAtRestEncryptionS3": true, + "enableAtRestEncryptionEBS": true, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configurations: replace with your actual account and region for deployment.", + "//": "You can create multiple environment configurations, e.g., 'dev', 'staging', 'prod'.", + "//": "Example for prod: aws://YOUR_ACCOUNT_ID/YOUR_REGION", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "SimpleImageWebApp", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "enableAdvancedLogging": true, + "enableDetailedMonitoring": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..36ffe953 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/cdk.ts @@ -0,0 +1,345 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; +import * as targets from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; +import * as cloudfront from 'aws-cdk-lib/aws-cloudfront'; +import * as origins from 'aws-cdk-lib/aws-cloudfront-origins'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as logs from 'aws-cdk-lib/aws-logs'; + +/** + * Properties for the SimpleImageWebAppStack. + */ +export interface SimpleImageWebAppStackProps extends cdk.StackProps { + readonly vpcId?: string; + readonly availabilityZones?: string[]; + readonly logRetentionDays: number; + readonly applicationName: string; + readonly environmentType: string; + readonly ec2InstanceType: string; + readonly ec2KeyPairName?: string; + readonly enableTransitEncryption: boolean; + readonly enableAtRestEncryptionS3: boolean; + readonly enableAtRestEncryptionEBS: boolean; + readonly enableAdvancedLogging: boolean; + readonly enableDetailedMonitoring: boolean; +} + +/** + * The main CDK stack for the Simple Image Web Application. + * This stack provisions a VPC (or uses an existing one), an S3 bucket for image storage, + * an EC2 instance to act as a web server, an Application Load Balancer to distribute traffic, + * and a CloudFront distribution for content delivery and caching. + */ +export class SimpleImageWebAppStack extends cdk.Stack { + public readonly imageBucket: s3.Bucket; + public readonly webServerInstance: ec2.Instance; + public readonly alb: elbv2.ApplicationLoadBalancer; + public readonly cloudfrontDistribution: cloudfront.Distribution; + public readonly webServerRole: iam.Role; + + constructor(scope: Construct, id: string, props: SimpleImageWebAppStackProps) { + super(scope, id, props); + + // --- VPC Configuration --- + // Use an existing VPC if vpcId is provided, otherwise create a new one. + const vpc = props.vpcId + ? ec2.Vpc.fromLookup(this, 'ImportedVpc', { vpcId: props.vpcId }) + : new ec2.Vpc(this, 'AppVpc', { + maxAzs: props.availabilityZones?.length || 2, // Default to 2 AZs if not specified + subnetConfiguration: [ + { + cidrMask: 24, + name: 'public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'private', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + natGateways: 1, // Cost optimization for dev/test; consider more for prod + }); + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-3310'); + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + + vpc.addFlowLog('AppVpcFlowLog'); + + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { + vpc, + description: 'Security group for the Application LoadBalancer', + allowAllOutbound: true, + }); + // Allow HTTP/HTTPS traffic from anywhere (0.0.0.0/0) to the ALB + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP traffic from anywhere'); + albSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS traffic from anywhere'); + + // Security Group for the EC2 Web Server + const webServerSg = new ec2.SecurityGroup(this, 'WebServerSg', { + vpc, + description: 'Security group for the EC2 Web Server', + allowAllOutbound: true, + }); + // Allow traffic from the ALB to the EC2 instance on the application port (e.g., 80 or 8080) + // Assuming the web server listens on port 80 + webServerSg.addIngressRule(albSg, ec2.Port.tcp(80), 'Allow HTTP traffic from ALB'); + // Optionally, allow SSH access from a specific IP or bastion host (recommended for production) + // For simplicity, allowing from anywhere. RESTRICT THIS IN PRODUCTION. + if (props.ec2KeyPairName) { + webServerSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(22), 'Allow SSH access'); + } + + // --- IAM Role for EC2 Instance --- + this.webServerRole = new iam.Role(this, 'WebServerRole', { + assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), + description: 'IAM role for the EC2 web server to access S3 and other services', + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'), // For Systems Manager access + ], + }); + + // --- S3 Bucket for Image Storage --- + this.imageBucket = new s3.Bucket(this, 'ImageStorageBucket', { + bucketName: `${props.applicationName}-images-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: props.environmentType === 'production' ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: props.environmentType !== 'production', + versioned: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain images for two years + noncurrentVersionExpiration: cdk.Duration.days(30), // Delete old versions after 30 days + }, + ], + }); + + // Grant the EC2 web server role permissions to read/write to the S3 bucket + // This includes GetObject for downloads and PutObject for uploads, and also for generating presigned URLs + this.imageBucket.grantReadWrite(this.webServerRole); + // More specific permissions for pre-signed URLs if needed (e.g., only allow PutObject with specific content types) + this.webServerRole.addToPolicy( + new iam.PolicyStatement({ + actions: ['s3:GetObject', 's3:PutObject', 's3:ListBucket'], // s3:ListBucket might be needed depending on app logic + resources: [this.imageBucket.bucketArn, `${this.imageBucket.bucketArn}/*`], + }) + ); + + // --- EC2 Instance (Web Server) --- + // Define the machine image (AMI) - using latest Amazon Linux 2 + const ami = ec2.MachineImage.latestAmazonLinux2({ + cpuType: props.ec2InstanceType.startsWith('t2') || props.ec2InstanceType.startsWith('t3') || props.ec2InstanceType.startsWith('t4g') + ? ec2.AmazonLinuxCpuType.X86_64 // Or ARM64 if instance type supports it + : ec2.AmazonLinuxCpuType.X86_64, // Default, adjust as needed + }); + + this.webServerInstance = new ec2.Instance(this, 'WebServerInstance', { + vpc, + instanceType: new ec2.InstanceType(props.ec2InstanceType), + machineImage: ami, + securityGroup: webServerSg, + role: this.webServerRole, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Place instances in private subnets + }, + keyName: props.ec2KeyPairName, // Make sure this key pair exists in your account/region + blockDevices: [ + { + deviceName: '/dev/xvda', // Root volume + volume: ec2.BlockDeviceVolume.ebs(50, { // 50 GB EBS volume + encrypted: props.enableAtRestEncryptionEBS, + volumeType: ec2.EbsDeviceVolumeType.GP3, + deleteOnTermination: true, // Ensure EBS volume is deleted when instance is terminated + }), + }, + ], + // User data to install a simple web server (e.g., Apache or Nginx) + // Replace with your actual application deployment script + userData: ec2.UserData.forLinux({ + shebang: '#!/bin/bash\nset -euxo pipefail\n# Install Apache\nyum update -y\nyum install -y httpd\n# Start Apache and enable it on boot\nsystemctl start httpd\nsystemctl enable httpd\n# Create a simple index.html page\necho "

Hello from EC2 instance in $(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone)

" > /var/www/html/index.html\n# Placeholder for application deployment script\n# Your application should be configured to serve content and handle S3 pre-signed URLs' + }), + requireImdsv2: true, // Enforce IMDSv2 for enhanced security + }); + + // --- Application Load Balancer (ALB) --- + this.alb = new elbv2.ApplicationLoadBalancer(this, 'AppALB', { + vpc, + internetFacing: true, + securityGroup: albSg, + vpcSubnets: { + subnetType: ec2.SubnetType.PUBLIC, // ALB in public subnets + }, + }); + + // Enable access logs for ALB + if (props.enableAdvancedLogging) { + const albLogBucket = new s3.Bucket(this, 'AlbLogsBucket', { + bucketName: `${props.applicationName}-alb-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, // Or RETAIN for production + autoDeleteObjects: true, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + enforceSSL: props.enableTransitEncryption, + intelligentTieringConfigurations: [{ + name: 'archiveOldLogs', + deepArchiveAccessTierTime: cdk.Duration.days(180), + prefix: 'archive/', // Example: move logs under 'archive/' prefix to deep archive + tags: [{key: 'log-type', value: 'alb-access-logs'}] + }] + }); + this.alb.logAccessLogs(albLogBucket); + } + + // Create a listener for the ALB on port 80 (or 443 if using HTTPS) + const listener = this.alb.addListener('HttpListener', { + port: 80, // For HTTPS, use port 443 and specify certificates + open: false, // Do not automatically open SG, already configured by albSg + protocol: elbv2.ApplicationProtocol.HTTP, + // For HTTPS: + // protocol: elbv2.ApplicationProtocol.HTTPS, + // certificates: [elbv2.ListenerCertificate.fromArn('your-certificate-arn')], + }); + + // Create a target group for the EC2 instance + const targetGroup = new elbv2.ApplicationTargetGroup(this, 'WebServerTargetGroup', { + vpc, + port: 80, // Port your application listens on + protocol: elbv2.ApplicationProtocol.HTTP, + targets: [new targets.InstanceTarget(this.webServerInstance)], + healthCheck: { + path: '/', // Health check path for your application + interval: cdk.Duration.seconds(15), + timeout: cdk.Duration.seconds(5), + healthyThresholdCount: 3, + unhealthyThresholdCount: 2, + protocol: elbv2.Protocol.HTTP, + }, + }); + + listener.addTargetGroups('DefaultTargetGroup', { + targetGroups: [targetGroup], + }); + + // --- CloudFront Distribution --- + // Default cache policy - optimized for web applications + const cachePolicy = props.enableDetailedMonitoring // Example usage of a feature flag + ? cloudfront.CachePolicy.CACHING_OPTIMIZED + : cloudfront.CachePolicy.CACHING_DISABLED; // Or a custom policy + + const originRequestPolicy = cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER; + + this.cloudfrontDistribution = new cloudfront.Distribution(this, 'AppCdn', { + comment: `${props.applicationName} CloudFront Distribution`, + defaultBehavior: { + origin: new origins.LoadBalancerV2Origin(this.alb, { + protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // Traffic between CF and ALB is HTTP + // customHeaders: { 'X-My-Custom-Header': 'some-value' } // Optional custom headers + }), + viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + allowedMethods: cloudfront.AllowedMethods.ALLOW_ALL, + cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD_OPTIONS, + cachePolicy: cachePolicy, + originRequestPolicy: originRequestPolicy, + compress: true, + }, + // Consider adding a custom domain and ACM certificate for production + // domainNames: ['www.yourdomain.com'], + // certificate: acm.Certificate.fromCertificateArn(this, 'MyCert', 'arn:aws:acm:us-east-1:ACCOUNT:certificate/CERT_ID'), + defaultRootObject: 'index.html', // Or your application's default entry point + enableLogging: props.enableAdvancedLogging, // Enable access logs for CloudFront + logBucket: props.enableAdvancedLogging ? new s3.Bucket(this, 'CdnLogsBucket', { + bucketName: `${props.applicationName}-cdn-logs-${this.account}-${this.region}`.toLowerCase(), + removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: props.enableAtRestEncryptionS3 ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + }) : undefined, + logFilePrefix: 'cloudfront-access-logs/', + priceClass: cloudfront.PriceClass.PRICE_CLASS_200, // wider edge coverage + minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021, + }); + + const replicationRole = new iam.Role(this, 'ImageReplicationRole', { + assumedBy: new iam.ServicePrincipal('s3.amazonaws.com'), + }); + replicationRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:ReplicateObject', 's3:ReplicateDelete'], + resources: ['arn:aws:s3:::partner-mirror-222233334444/*'], // named foreign bucket + })); + + // --- Outputs --- + new cdk.CfnOutput(this, 'ImageBucketName', { + value: this.imageBucket.bucketName, + description: 'Name of the S3 bucket for image storage.', + }); + + new cdk.CfnOutput(this, 'ALBDnsName', { + value: this.alb.loadBalancerDnsName, + description: 'DNS name of the Application Load Balancer.', + }); + + new cdk.CfnOutput(this, 'CloudFrontDistributionDomainName', { + value: this.cloudfrontDistribution.distributionDomainName, + description: 'Domain name of the CloudFront distribution.', + }); + + new cdk.CfnOutput(this, 'WebServerInstanceId', { + value: this.webServerInstance.instanceId, + description: 'ID of the EC2 Web Server instance. Use for SSH if key pair is configured.', + }); + + if (props.ec2KeyPairName) { + new cdk.CfnOutput(this, 'WebServerSSHCommand', { + value: `ssh -i "~/.ssh/${props.ec2KeyPairName}.pem" ec2-user@${this.webServerInstance.instancePublicDnsName || this.webServerInstance.instancePrivateDnsName}`, + description: 'Command to SSH into the Web Server instance (requires public IP or Bastion). Update key path if needed.', + }); + } + } +} + +// --- CDK Application Entry Point --- +const app = new cdk.App(); + +// Retrieve context values from cdk.json or command line +const contextProps = { + vpcId: app.node.tryGetContext('vpcId'), + availabilityZones: app.node.tryGetContext('availabilityZones'), + logRetentionDays: app.node.tryGetContext('logRetentionDays') || 30, + applicationName: app.node.tryGetContext('applicationName') || 'SimpleImageWebApp', + environmentType: app.node.tryGetContext('environmentType') || 'development', + ec2InstanceType: app.node.tryGetContext('ec2InstanceType') || 't3.micro', + ec2KeyPairName: app.node.tryGetContext('ec2KeyPairName'), // Can be undefined + enableTransitEncryption: app.node.tryGetContext('enableTransitEncryption') === 'true' || app.node.tryGetContext('enableTransitEncryption') === true || true, // default true + enableAtRestEncryptionS3: app.node.tryGetContext('enableAtRestEncryptionS3') === 'true' || app.node.tryGetContext('enableAtRestEncryptionS3') === true || true, + enableAtRestEncryptionEBS: app.node.tryGetContext('enableAtRestEncryptionEBS') === 'true' || app.node.tryGetContext('enableAtRestEncryptionEBS') === true || true, + enableAdvancedLogging: app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === 'true' || app.node.tryGetContext('featureFlags')?.enableAdvancedLogging === true || false, + enableDetailedMonitoring: app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === 'true' || app.node.tryGetContext('featureFlags')?.enableDetailedMonitoring === true || false, +}; + +// Define stack props, including the env from cdk.json +const stackProps: SimpleImageWebAppStackProps = { + ...contextProps, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT || app.node.tryGetContext('env')?.account || '123456789012', // Fallback placeholder + region: process.env.CDK_DEFAULT_REGION || app.node.tryGetContext('env')?.region || 'us-east-1', // Fallback placeholder + }, + tags: { + Project: contextProps.applicationName, + Environment: contextProps.environmentType, + ManagedBy: 'CDK', + ...(app.node.tryGetContext('tags') || {}), // Merge tags from cdk.json + }, + description: `Stack for ${contextProps.applicationName} in ${contextProps.environmentType} environment.`, +}; + +new SimpleImageWebAppStack(app, `${contextProps.applicationName}Stack-${contextProps.environmentType}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/package.json new file mode 100644 index 00000000..e5e953dc --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "simple-image-web-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/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/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/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/commit.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/commit.json new file mode 100644 index 00000000..3b2b35db --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "5756e71", + "full_sha": "5756e71569a002770fd5207752097a0fbcdeca21", + "parent": "8f9b19a", + "message": "chore: enable vpc flow logs", + "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 | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/diff.patch b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/diff.patch new file mode 100644 index 00000000..8166644d --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 738b7a2..36ffe95 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -69,6 +69,8 @@ export class SimpleImageWebAppStack extends cdk.Stack { + cdk.Tags.of(this).add('Owner', 'web-platform'); + cdk.Tags.of(this).add('DataClass', 'user-images'); + ++ vpc.addFlowLog('AppVpcFlowLog'); ++ + // --- Security Group Definitions --- + // Security Group for the Application Load Balancer (ALB) + const albSg = new ec2.SecurityGroup(this, 'AlbSg', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/provenance.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/provenance.json new file mode 100644 index 00000000..c4d9ec52 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/commits/provenance.json @@ -0,0 +1,197 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "0cac3cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "4542377", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "9adaf6c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "0cfdf00", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "35f0a9e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "d4e7e9b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "10163b2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "8101537", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "43cabbb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 38, + "sha": "ded0767", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "1fa5f97", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 40, + "sha": "8f9b19a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SimpleImageWebAppStack-development.assets.json", + "SimpleImageWebAppStack-development.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 44, + "sha": "5756e71", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/metadata.yaml b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/metadata.yaml new file mode 100644 index 00000000..37bb6843 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/metadata.yaml @@ -0,0 +1,176 @@ +benchmark: T4 +family: Trust Boundary Drift +drift_name: Cross-Account Image Replication +base_repo: simple-image-upload-download-website +stack: SimpleImageWebAppStack-development +description: "A replication role targets a named foreign-account bucket (C7, Mild), then any bucket\ + \ (C11, Severe) \u2014 user images are copied across the account boundary." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + simple-image-upload-download-website baseline 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 the baseline 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: 'Replication destination: named foreign bucket -> any bucket (''*'').' +commit_count: 13 +commits: + C1: + sha: 0cac3cd + label: V1 + message: 'feat: simple image upload/download website baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: d4e7e9b + label: VB + message: 'chore: tighten alb health check interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C3: + sha: 10163b2 + label: VB + message: 'chore: extend image retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '8101537' + label: VB + message: 'chore: raise healthy threshold count' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: target group only + C5: + sha: 43cabbb + label: VB + message: 'chore: widen cloudfront price class' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: edge coverage only + C6: + sha: ded0767 + label: VB + message: 'chore: grow root volume to 50gb' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: block device size only + C7: + sha: 1fa5f97 + label: V2 + message: 'feat: replicate images to the partner mirror bucket' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: s3_replication_named + path: ImageReplicationRole -> s3:ReplicateObject -> partner-mirror-222233334444 + controls_present: + - SingleForeignBucket + controls_absent: [] + component: ImageReplicationRole + transitive_reach: + - images copied to one foreign bucket + construct_ids: + - ImageReplicationRole + template_assertion: policy s3:ReplicateObject Resource = a named foreign bucket ARN + note: Cross-account replication, scoped to one destination. Mild. + C8: + sha: 8f9b19a + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C9: + sha: 5756e71 + label: VB + message: 'chore: enable vpc flow logs' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: security improvement decoy + C10: + sha: '4542377' + label: VB + message: 'chore: add vpc id stack output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C11: + sha: 9adaf6c + label: V3 + message: 'fix: widen replication destination for multi-region mirrors' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: s3_replication_any + path: ImageReplicationRole -> s3:ReplicateObject -> * (any bucket) + controls_present: [] + controls_removed: + - SingleForeignBucket + component: ImageReplicationRole + transitive_reach: + - images replicable to any bucket in any account + construct_ids: + - ImageReplicationRole + template_assertion: ReplicateObject Resource becomes arn:aws:s3:::* + note: User images can leave to an arbitrary destination across the account boundary. + C12: + sha: 0cfdf00 + label: VB + message: 'docs: add replication runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C13: + sha: 35f0a9e + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- images are user content at C1 +- no egress destination allowlist at C1 +scoring: + drift_commits: + - C7 + - C11 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C6 + - C8 + - C9 + - C10 + - C12 + - C13 + false_positive_probes: + - C8 + - C9 + state_vs_event_probes: + - C12 + - C13 + severity_axis: replication destination breadth (named foreign bucket -> any) + note: Cross-account data-egress axis; images replicated off-account, then anywhere. diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/repository b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/repository new file mode 160000 index 00000000..35f0a9e7 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/repository @@ -0,0 +1 @@ +Subproject commit 35f0a9e79002fef9e9284a213f873a24d2f1eeb7 diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/template_analysis.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/template_analysis.json new file mode 100644 index 00000000..fc4a8dae --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/T4/template_analysis.json @@ -0,0 +1,383 @@ +{ + "benchmark": "T4", + "drift_name": "Cross-Account Image Replication", + "commits": { + "C1": { + "sha": "0cac3cd", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "d4e7e9b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "10163b2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "8101537", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "43cabbb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "ded0767", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 38, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "1fa5f97", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 41, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "8f9b19a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 40, + "template_diff_lines": 270, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "5756e71", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 44, + "template_diff_lines": 139, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "4542377", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 44, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "9adaf6c", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 44, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "0cfdf00", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 44, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C13": { + "sha": "35f0a9e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 44, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "AppALBDBE7026A": "internet-facing" + }, + "open_ingress_sgs": [ + "AlbSg1155C1BE", + "WebServerSg02D2F8DD" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AmazonSSMManagedInstanceCore", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/synth_context.json b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/synth_context.json new file mode 100644 index 00000000..5ae06c75 --- /dev/null +++ b/ctm-iac/cdk-dataset/simple-image-upload-download-website/versionzied_gt/synth_context.json @@ -0,0 +1 @@ +{"applicationName":"SimpleImageWebApp","environmentType":"development","ec2InstanceType":"t3.micro","ec2KeyPairName":"app-key","logRetentionDays":30,"enableTransitEncryption":true,"enableAtRestEncryptionS3":true,"enableAtRestEncryptionEBS":true,"availabilityZones":["us-east-1a","us-east-1b"]} \ No newline at end of file