From 47003f4d1bfb116543daa138ac6fd78d7957dbb7 Mon Sep 17 00:00:00 2001 From: Joshua Bahirvani Date: Sun, 6 Sep 2026 01:06:41 +0530 Subject: [PATCH] drift-dataset-automated-sec-rep --- .../versionized_gt/D1/README.md | 144 ++++++ .../versionized_gt/D1/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../D1/commits/C1/cdk/package.json | 15 + .../D1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C1/commit.json | 26 ++ .../versionized_gt/D1/commits/C1/diff.patch | 0 .../versionized_gt/D1/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../D1/commits/C2/cdk/package.json | 15 + .../D1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C2/commit.json | 20 + .../versionized_gt/D1/commits/C2/diff.patch | 13 + .../versionized_gt/D1/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../D1/commits/C3/cdk/package.json | 15 + .../D1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C3/commit.json | 20 + .../versionized_gt/D1/commits/C3/diff.patch | 13 + .../versionized_gt/D1/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C4/cdk/cdk.ts | 386 ++++++++++++++++ .../D1/commits/C4/cdk/package.json | 15 + .../D1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C4/commit.json | 20 + .../versionized_gt/D1/commits/C4/diff.patch | 12 + .../versionized_gt/D1/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C5/cdk/cdk.ts | 391 ++++++++++++++++ .../D1/commits/C5/cdk/package.json | 15 + .../D1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C5/commit.json | 20 + .../versionized_gt/D1/commits/C5/diff.patch | 16 + .../versionized_gt/D1/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C6/cdk/cdk.ts | 391 ++++++++++++++++ .../D1/commits/C6/cdk/package.json | 15 + .../D1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C6/commit.json | 20 + .../versionized_gt/D1/commits/C6/diff.patch | 12 + .../versionized_gt/D1/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/D1/commits/C7/cdk/cdk.ts | 391 ++++++++++++++++ .../D1/commits/C7/cdk/package.json | 15 + .../D1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C7/commit.json | 20 + .../versionized_gt/D1/commits/C7/diff.patch | 13 + .../versionized_gt/D1/commits/provenance.json | 107 +++++ .../versionized_gt/D1/metadata.yaml | 124 +++++ .../versionized_gt/D1/repository | 1 + .../versionized_gt/D1/synth_context.json | 1 + .../versionized_gt/D1/template_analysis.json | 216 +++++++++ .../versionized_gt/D2/README.md | 150 ++++++ .../versionized_gt/D2/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../D2/commits/C1/cdk/package.json | 15 + .../D2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C1/commit.json | 26 ++ .../versionized_gt/D2/commits/C1/diff.patch | 0 .../versionized_gt/D2/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../D2/commits/C2/cdk/package.json | 15 + .../D2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C2/commit.json | 20 + .../versionized_gt/D2/commits/C2/diff.patch | 13 + .../versionized_gt/D2/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C3/cdk/cdk.ts | 386 ++++++++++++++++ .../D2/commits/C3/cdk/package.json | 15 + .../D2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C3/commit.json | 20 + .../versionized_gt/D2/commits/C3/diff.patch | 12 + .../versionized_gt/D2/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C4/cdk/cdk.ts | 391 ++++++++++++++++ .../D2/commits/C4/cdk/package.json | 15 + .../D2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C4/commit.json | 20 + .../versionized_gt/D2/commits/C4/diff.patch | 16 + .../versionized_gt/D2/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C5/cdk/cdk.ts | 391 ++++++++++++++++ .../D2/commits/C5/cdk/package.json | 15 + .../D2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C5/commit.json | 20 + .../versionized_gt/D2/commits/C5/diff.patch | 13 + .../versionized_gt/D2/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C6/cdk/cdk.ts | 391 ++++++++++++++++ .../D2/commits/C6/cdk/package.json | 15 + .../D2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C6/commit.json | 20 + .../versionized_gt/D2/commits/C6/diff.patch | 13 + .../versionized_gt/D2/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C7/cdk/cdk.ts | 391 ++++++++++++++++ .../D2/commits/C7/cdk/package.json | 15 + .../D2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C7/commit.json | 20 + .../versionized_gt/D2/commits/C7/diff.patch | 12 + .../versionized_gt/D2/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C8/cdk/cdk.ts | 392 ++++++++++++++++ .../D2/commits/C8/cdk/package.json | 15 + .../D2/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C8/commit.json | 20 + .../versionized_gt/D2/commits/C8/diff.patch | 12 + .../versionized_gt/D2/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/D2/commits/C9/cdk/cdk.ts | 392 ++++++++++++++++ .../D2/commits/C9/cdk/package.json | 15 + .../D2/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C9/commit.json | 20 + .../versionized_gt/D2/commits/C9/diff.patch | 11 + .../versionized_gt/D2/commits/provenance.json | 137 ++++++ .../versionized_gt/D2/metadata.yaml | 143 ++++++ .../versionized_gt/D2/repository | 1 + .../versionized_gt/D2/synth_context.json | 1 + .../versionized_gt/D2/template_analysis.json | 276 ++++++++++++ .../versionized_gt/D3/README.md | 159 +++++++ .../versionized_gt/D3/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../D3/commits/C1/cdk/package.json | 15 + .../D3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C1/commit.json | 26 ++ .../versionized_gt/D3/commits/C1/diff.patch | 0 .../D3/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C10/cdk/cdk.ts | 392 ++++++++++++++++ .../D3/commits/C10/cdk/package.json | 15 + .../D3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C10/commit.json | 20 + .../versionized_gt/D3/commits/C10/diff.patch | 12 + .../D3/commits/C11/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C11/cdk/cdk.ts | 392 ++++++++++++++++ .../D3/commits/C11/cdk/package.json | 15 + .../D3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C11/commit.json | 20 + .../versionized_gt/D3/commits/C11/diff.patch | 13 + .../D3/commits/C12/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C12/cdk/cdk.ts | 392 ++++++++++++++++ .../D3/commits/C12/cdk/package.json | 15 + .../D3/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C12/commit.json | 20 + .../versionized_gt/D3/commits/C12/diff.patch | 11 + .../versionized_gt/D3/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../D3/commits/C2/cdk/package.json | 15 + .../D3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C2/commit.json | 20 + .../versionized_gt/D3/commits/C2/diff.patch | 13 + .../versionized_gt/D3/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../D3/commits/C3/cdk/package.json | 15 + .../D3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C3/commit.json | 20 + .../versionized_gt/D3/commits/C3/diff.patch | 13 + .../versionized_gt/D3/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C4/cdk/cdk.ts | 384 ++++++++++++++++ .../D3/commits/C4/cdk/package.json | 15 + .../D3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C4/commit.json | 20 + .../versionized_gt/D3/commits/C4/diff.patch | 13 + .../versionized_gt/D3/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C5/cdk/cdk.ts | 386 ++++++++++++++++ .../D3/commits/C5/cdk/package.json | 15 + .../D3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C5/commit.json | 20 + .../versionized_gt/D3/commits/C5/diff.patch | 12 + .../versionized_gt/D3/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C6/cdk/cdk.ts | 391 ++++++++++++++++ .../D3/commits/C6/cdk/package.json | 15 + .../D3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C6/commit.json | 20 + .../versionized_gt/D3/commits/C6/diff.patch | 16 + .../versionized_gt/D3/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C7/cdk/cdk.ts | 391 ++++++++++++++++ .../D3/commits/C7/cdk/package.json | 15 + .../D3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C7/commit.json | 20 + .../versionized_gt/D3/commits/C7/diff.patch | 13 + .../versionized_gt/D3/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C8/cdk/cdk.ts | 392 ++++++++++++++++ .../D3/commits/C8/cdk/package.json | 15 + .../D3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C8/commit.json | 20 + .../versionized_gt/D3/commits/C8/diff.patch | 12 + .../versionized_gt/D3/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/D3/commits/C9/cdk/cdk.ts | 392 ++++++++++++++++ .../D3/commits/C9/cdk/package.json | 15 + .../D3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C9/commit.json | 20 + .../versionized_gt/D3/commits/C9/diff.patch | 12 + .../versionized_gt/D3/commits/provenance.json | 182 ++++++++ .../versionized_gt/D3/metadata.yaml | 168 +++++++ .../versionized_gt/D3/repository | 1 + .../versionized_gt/D3/synth_context.json | 1 + .../versionized_gt/D3/template_analysis.json | 366 +++++++++++++++ .../versionized_gt/D4/README.md | 138 ++++++ .../versionized_gt/D4/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/D4/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../D4/commits/C1/cdk/package.json | 15 + .../D4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C1/commit.json | 26 ++ .../versionized_gt/D4/commits/C1/diff.patch | 0 .../versionized_gt/D4/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/D4/commits/C2/cdk/cdk.ts | 386 ++++++++++++++++ .../D4/commits/C2/cdk/package.json | 15 + .../D4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C2/commit.json | 20 + .../versionized_gt/D4/commits/C2/diff.patch | 12 + .../versionized_gt/D4/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/D4/commits/C3/cdk/cdk.ts | 391 ++++++++++++++++ .../D4/commits/C3/cdk/package.json | 15 + .../D4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C3/commit.json | 20 + .../versionized_gt/D4/commits/C3/diff.patch | 16 + .../versionized_gt/D4/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/D4/commits/C4/cdk/cdk.ts | 391 ++++++++++++++++ .../D4/commits/C4/cdk/package.json | 15 + .../D4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C4/commit.json | 20 + .../versionized_gt/D4/commits/C4/diff.patch | 12 + .../versionized_gt/D4/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/D4/commits/C5/cdk/cdk.ts | 391 ++++++++++++++++ .../D4/commits/C5/cdk/package.json | 15 + .../D4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C5/commit.json | 20 + .../versionized_gt/D4/commits/C5/diff.patch | 13 + .../versionized_gt/D4/commits/provenance.json | 77 ++++ .../versionized_gt/D4/metadata.yaml | 103 +++++ .../versionized_gt/D4/repository | 1 + .../versionized_gt/D4/synth_context.json | 1 + .../versionized_gt/D4/template_analysis.json | 156 +++++++ .../versionized_gt/E1/README.md | 136 ++++++ .../versionized_gt/E1/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/E1/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../E1/commits/C1/cdk/package.json | 15 + .../E1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C1/commit.json | 26 ++ .../versionized_gt/E1/commits/C1/diff.patch | 0 .../versionized_gt/E1/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/E1/commits/C2/cdk/cdk.ts | 387 ++++++++++++++++ .../E1/commits/C2/cdk/package.json | 15 + .../E1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C2/commit.json | 20 + .../versionized_gt/E1/commits/C2/diff.patch | 16 + .../versionized_gt/E1/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/E1/commits/C3/cdk/cdk.ts | 392 ++++++++++++++++ .../E1/commits/C3/cdk/package.json | 15 + .../E1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C3/commit.json | 20 + .../versionized_gt/E1/commits/C3/diff.patch | 16 + .../versionized_gt/E1/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/E1/commits/C4/cdk/cdk.ts | 392 ++++++++++++++++ .../E1/commits/C4/cdk/package.json | 15 + .../E1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C4/commit.json | 20 + .../versionized_gt/E1/commits/C4/diff.patch | 16 + .../versionized_gt/E1/commits/provenance.json | 62 +++ .../versionized_gt/E1/metadata.yaml | 98 ++++ .../versionized_gt/E1/repository | 1 + .../versionized_gt/E1/synth_context.json | 1 + .../versionized_gt/E1/template_analysis.json | 126 ++++++ .../versionized_gt/E2/README.md | 144 ++++++ .../versionized_gt/E2/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../E2/commits/C1/cdk/package.json | 15 + .../E2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C1/commit.json | 26 ++ .../versionized_gt/E2/commits/C1/diff.patch | 0 .../versionized_gt/E2/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../E2/commits/C2/cdk/package.json | 15 + .../E2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C2/commit.json | 20 + .../versionized_gt/E2/commits/C2/diff.patch | 13 + .../versionized_gt/E2/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../E2/commits/C3/cdk/package.json | 15 + .../E2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C3/commit.json | 20 + .../versionized_gt/E2/commits/C3/diff.patch | 13 + .../versionized_gt/E2/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C4/cdk/cdk.ts | 389 ++++++++++++++++ .../E2/commits/C4/cdk/package.json | 15 + .../E2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C4/commit.json | 20 + .../versionized_gt/E2/commits/C4/diff.patch | 16 + .../versionized_gt/E2/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C5/cdk/cdk.ts | 389 ++++++++++++++++ .../E2/commits/C5/cdk/package.json | 15 + .../E2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C5/commit.json | 20 + .../versionized_gt/E2/commits/C5/diff.patch | 13 + .../versionized_gt/E2/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C6/cdk/cdk.ts | 389 ++++++++++++++++ .../E2/commits/C6/cdk/package.json | 15 + .../E2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C6/commit.json | 20 + .../versionized_gt/E2/commits/C6/diff.patch | 13 + .../versionized_gt/E2/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/E2/commits/C7/cdk/cdk.ts | 389 ++++++++++++++++ .../E2/commits/C7/cdk/package.json | 15 + .../E2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C7/commit.json | 20 + .../versionized_gt/E2/commits/C7/diff.patch | 13 + .../versionized_gt/E2/commits/provenance.json | 107 +++++ .../versionized_gt/E2/metadata.yaml | 121 +++++ .../versionized_gt/E2/repository | 1 + .../versionized_gt/E2/synth_context.json | 1 + .../versionized_gt/E2/template_analysis.json | 216 +++++++++ .../versionized_gt/E3/README.md | 142 ++++++ .../versionized_gt/E3/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/E3/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../E3/commits/C1/cdk/package.json | 15 + .../E3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C1/commit.json | 26 ++ .../versionized_gt/E3/commits/C1/diff.patch | 0 .../versionized_gt/E3/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/E3/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../E3/commits/C2/cdk/package.json | 15 + .../E3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C2/commit.json | 20 + .../versionized_gt/E3/commits/C2/diff.patch | 13 + .../versionized_gt/E3/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/E3/commits/C3/cdk/cdk.ts | 389 ++++++++++++++++ .../E3/commits/C3/cdk/package.json | 15 + .../E3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C3/commit.json | 20 + .../versionized_gt/E3/commits/C3/diff.patch | 16 + .../versionized_gt/E3/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/E3/commits/C4/cdk/cdk.ts | 389 ++++++++++++++++ .../E3/commits/C4/cdk/package.json | 15 + .../E3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C4/commit.json | 20 + .../versionized_gt/E3/commits/C4/diff.patch | 13 + .../versionized_gt/E3/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/E3/commits/C5/cdk/cdk.ts | 389 ++++++++++++++++ .../E3/commits/C5/cdk/package.json | 15 + .../E3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C5/commit.json | 20 + .../versionized_gt/E3/commits/C5/diff.patch | 13 + .../versionized_gt/E3/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/E3/commits/C6/cdk/cdk.ts | 389 ++++++++++++++++ .../E3/commits/C6/cdk/package.json | 15 + .../E3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C6/commit.json | 20 + .../versionized_gt/E3/commits/C6/diff.patch | 13 + .../versionized_gt/E3/commits/provenance.json | 92 ++++ .../versionized_gt/E3/metadata.yaml | 113 +++++ .../versionized_gt/E3/repository | 1 + .../versionized_gt/E3/synth_context.json | 1 + .../versionized_gt/E3/template_analysis.json | 186 ++++++++ .../versionized_gt/E4/README.md | 153 +++++++ .../versionized_gt/E4/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../E4/commits/C1/cdk/package.json | 15 + .../E4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C1/commit.json | 26 ++ .../versionized_gt/E4/commits/C1/diff.patch | 0 .../E4/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C10/cdk/cdk.ts | 398 ++++++++++++++++ .../E4/commits/C10/cdk/package.json | 15 + .../E4/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C10/commit.json | 20 + .../versionized_gt/E4/commits/C10/diff.patch | 11 + .../versionized_gt/E4/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../E4/commits/C2/cdk/package.json | 15 + .../E4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C2/commit.json | 20 + .../versionized_gt/E4/commits/C2/diff.patch | 13 + .../versionized_gt/E4/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../E4/commits/C3/cdk/package.json | 15 + .../E4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C3/commit.json | 20 + .../versionized_gt/E4/commits/C3/diff.patch | 13 + .../versionized_gt/E4/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C4/cdk/cdk.ts | 392 ++++++++++++++++ .../E4/commits/C4/cdk/package.json | 15 + .../E4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C4/commit.json | 20 + .../versionized_gt/E4/commits/C4/diff.patch | 19 + .../versionized_gt/E4/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C5/cdk/cdk.ts | 397 ++++++++++++++++ .../E4/commits/C5/cdk/package.json | 15 + .../E4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C5/commit.json | 20 + .../versionized_gt/E4/commits/C5/diff.patch | 16 + .../versionized_gt/E4/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C6/cdk/cdk.ts | 397 ++++++++++++++++ .../E4/commits/C6/cdk/package.json | 15 + .../E4/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C6/commit.json | 20 + .../versionized_gt/E4/commits/C6/diff.patch | 13 + .../versionized_gt/E4/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C7/cdk/cdk.ts | 397 ++++++++++++++++ .../E4/commits/C7/cdk/package.json | 15 + .../E4/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C7/commit.json | 20 + .../versionized_gt/E4/commits/C7/diff.patch | 13 + .../versionized_gt/E4/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C8/cdk/cdk.ts | 398 ++++++++++++++++ .../E4/commits/C8/cdk/package.json | 15 + .../E4/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C8/commit.json | 20 + .../versionized_gt/E4/commits/C8/diff.patch | 12 + .../versionized_gt/E4/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/E4/commits/C9/cdk/cdk.ts | 398 ++++++++++++++++ .../E4/commits/C9/cdk/package.json | 15 + .../E4/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C9/commit.json | 20 + .../versionized_gt/E4/commits/C9/diff.patch | 13 + .../versionized_gt/E4/commits/provenance.json | 152 +++++++ .../versionized_gt/E4/metadata.yaml | 150 ++++++ .../versionized_gt/E4/repository | 1 + .../versionized_gt/E4/synth_context.json | 1 + .../versionized_gt/E4/template_analysis.json | 306 +++++++++++++ .../versionized_gt/G1/README.md | 136 ++++++ .../versionized_gt/G1/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/G1/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../G1/commits/C1/cdk/package.json | 15 + .../G1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C1/commit.json | 26 ++ .../versionized_gt/G1/commits/C1/diff.patch | 0 .../versionized_gt/G1/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/G1/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../G1/commits/C2/cdk/package.json | 15 + .../G1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C2/commit.json | 20 + .../versionized_gt/G1/commits/C2/diff.patch | 13 + .../versionized_gt/G1/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/G1/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../G1/commits/C3/cdk/package.json | 15 + .../G1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C3/commit.json | 20 + .../versionized_gt/G1/commits/C3/diff.patch | 13 + .../versionized_gt/G1/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/G1/commits/C4/cdk/cdk.ts | 389 ++++++++++++++++ .../G1/commits/C4/cdk/package.json | 15 + .../G1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C4/commit.json | 20 + .../versionized_gt/G1/commits/C4/diff.patch | 16 + .../versionized_gt/G1/commits/provenance.json | 62 +++ .../versionized_gt/G1/metadata.yaml | 93 ++++ .../versionized_gt/G1/repository | 1 + .../versionized_gt/G1/synth_context.json | 1 + .../versionized_gt/G1/template_analysis.json | 126 ++++++ .../versionized_gt/G2/README.md | 147 ++++++ .../versionized_gt/G2/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../G2/commits/C1/cdk/package.json | 15 + .../G2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C1/commit.json | 26 ++ .../versionized_gt/G2/commits/C1/diff.patch | 0 .../versionized_gt/G2/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../G2/commits/C2/cdk/package.json | 15 + .../G2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C2/commit.json | 20 + .../versionized_gt/G2/commits/C2/diff.patch | 13 + .../versionized_gt/G2/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../G2/commits/C3/cdk/package.json | 15 + .../G2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C3/commit.json | 20 + .../versionized_gt/G2/commits/C3/diff.patch | 13 + .../versionized_gt/G2/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C4/cdk/cdk.ts | 384 ++++++++++++++++ .../G2/commits/C4/cdk/package.json | 15 + .../G2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C4/commit.json | 20 + .../versionized_gt/G2/commits/C4/diff.patch | 13 + .../versionized_gt/G2/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C5/cdk/cdk.ts | 389 ++++++++++++++++ .../G2/commits/C5/cdk/package.json | 15 + .../G2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C5/commit.json | 20 + .../versionized_gt/G2/commits/C5/diff.patch | 16 + .../versionized_gt/G2/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C6/cdk/cdk.ts | 389 ++++++++++++++++ .../G2/commits/C6/cdk/package.json | 15 + .../G2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C6/commit.json | 20 + .../versionized_gt/G2/commits/C6/diff.patch | 15 + .../versionized_gt/G2/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C7/cdk/cdk.ts | 390 ++++++++++++++++ .../G2/commits/C7/cdk/package.json | 15 + .../G2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C7/commit.json | 20 + .../versionized_gt/G2/commits/C7/diff.patch | 12 + .../versionized_gt/G2/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/G2/commits/C8/cdk/cdk.ts | 390 ++++++++++++++++ .../G2/commits/C8/cdk/package.json | 15 + .../G2/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C8/commit.json | 20 + .../versionized_gt/G2/commits/C8/diff.patch | 13 + .../versionized_gt/G2/commits/provenance.json | 122 +++++ .../versionized_gt/G2/metadata.yaml | 130 ++++++ .../versionized_gt/G2/repository | 1 + .../versionized_gt/G2/synth_context.json | 1 + .../versionized_gt/G2/template_analysis.json | 255 +++++++++++ .../versionized_gt/G3/README.md | 156 +++++++ .../versionized_gt/G3/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../G3/commits/C1/cdk/package.json | 15 + .../G3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C1/commit.json | 26 ++ .../versionized_gt/G3/commits/C1/diff.patch | 0 .../G3/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C10/cdk/cdk.ts | 396 ++++++++++++++++ .../G3/commits/C10/cdk/package.json | 15 + .../G3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C10/commit.json | 20 + .../versionized_gt/G3/commits/C10/diff.patch | 12 + .../G3/commits/C11/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C11/cdk/cdk.ts | 396 ++++++++++++++++ .../G3/commits/C11/cdk/package.json | 15 + .../G3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C11/commit.json | 20 + .../versionized_gt/G3/commits/C11/diff.patch | 13 + .../versionized_gt/G3/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../G3/commits/C2/cdk/package.json | 15 + .../G3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C2/commit.json | 20 + .../versionized_gt/G3/commits/C2/diff.patch | 13 + .../versionized_gt/G3/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../G3/commits/C3/cdk/package.json | 15 + .../G3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C3/commit.json | 20 + .../versionized_gt/G3/commits/C3/diff.patch | 13 + .../versionized_gt/G3/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C4/cdk/cdk.ts | 385 ++++++++++++++++ .../G3/commits/C4/cdk/package.json | 15 + .../G3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C4/commit.json | 20 + .../versionized_gt/G3/commits/C4/diff.patch | 11 + .../versionized_gt/G3/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C5/cdk/cdk.ts | 390 ++++++++++++++++ .../G3/commits/C5/cdk/package.json | 15 + .../G3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C5/commit.json | 20 + .../versionized_gt/G3/commits/C5/diff.patch | 16 + .../versionized_gt/G3/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C6/cdk/cdk.ts | 395 ++++++++++++++++ .../G3/commits/C6/cdk/package.json | 15 + .../G3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C6/commit.json | 20 + .../versionized_gt/G3/commits/C6/diff.patch | 16 + .../versionized_gt/G3/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C7/cdk/cdk.ts | 395 ++++++++++++++++ .../G3/commits/C7/cdk/package.json | 15 + .../G3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C7/commit.json | 20 + .../versionized_gt/G3/commits/C7/diff.patch | 13 + .../versionized_gt/G3/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C8/cdk/cdk.ts | 395 ++++++++++++++++ .../G3/commits/C8/cdk/package.json | 15 + .../G3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C8/commit.json | 20 + .../versionized_gt/G3/commits/C8/diff.patch | 12 + .../versionized_gt/G3/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/G3/commits/C9/cdk/cdk.ts | 395 ++++++++++++++++ .../G3/commits/C9/cdk/package.json | 15 + .../G3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C9/commit.json | 20 + .../versionized_gt/G3/commits/C9/diff.patch | 13 + .../versionized_gt/G3/commits/provenance.json | 167 +++++++ .../versionized_gt/G3/metadata.yaml | 158 +++++++ .../versionized_gt/G3/repository | 1 + .../versionized_gt/G3/synth_context.json | 1 + .../versionized_gt/G3/template_analysis.json | 343 ++++++++++++++ .../versionized_gt/G4/README.md | 162 +++++++ .../versionized_gt/G4/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../G4/commits/C1/cdk/package.json | 15 + .../G4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C1/commit.json | 26 ++ .../versionized_gt/G4/commits/C1/diff.patch | 0 .../G4/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C10/cdk/cdk.ts | 396 ++++++++++++++++ .../G4/commits/C10/cdk/package.json | 15 + .../G4/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C10/commit.json | 20 + .../versionized_gt/G4/commits/C10/diff.patch | 12 + .../G4/commits/C11/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C11/cdk/cdk.ts | 396 ++++++++++++++++ .../G4/commits/C11/cdk/package.json | 15 + .../G4/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C11/commit.json | 20 + .../versionized_gt/G4/commits/C11/diff.patch | 13 + .../G4/commits/C12/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C12/cdk/cdk.ts | 396 ++++++++++++++++ .../G4/commits/C12/cdk/package.json | 15 + .../G4/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C12/commit.json | 20 + .../versionized_gt/G4/commits/C12/diff.patch | 12 + .../G4/commits/C13/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C13/cdk/cdk.ts | 396 ++++++++++++++++ .../G4/commits/C13/cdk/package.json | 15 + .../G4/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C13/commit.json | 20 + .../versionized_gt/G4/commits/C13/diff.patch | 11 + .../versionized_gt/G4/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../G4/commits/C2/cdk/package.json | 15 + .../G4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C2/commit.json | 20 + .../versionized_gt/G4/commits/C2/diff.patch | 13 + .../versionized_gt/G4/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../G4/commits/C3/cdk/package.json | 15 + .../G4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C3/commit.json | 20 + .../versionized_gt/G4/commits/C3/diff.patch | 13 + .../versionized_gt/G4/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C4/cdk/cdk.ts | 385 ++++++++++++++++ .../G4/commits/C4/cdk/package.json | 15 + .../G4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C4/commit.json | 20 + .../versionized_gt/G4/commits/C4/diff.patch | 11 + .../versionized_gt/G4/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C5/cdk/cdk.ts | 385 ++++++++++++++++ .../G4/commits/C5/cdk/package.json | 15 + .../G4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C5/commit.json | 20 + .../versionized_gt/G4/commits/C5/diff.patch | 13 + .../versionized_gt/G4/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C6/cdk/cdk.ts | 390 ++++++++++++++++ .../G4/commits/C6/cdk/package.json | 15 + .../G4/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C6/commit.json | 20 + .../versionized_gt/G4/commits/C6/diff.patch | 16 + .../versionized_gt/G4/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C7/cdk/cdk.ts | 395 ++++++++++++++++ .../G4/commits/C7/cdk/package.json | 15 + .../G4/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C7/commit.json | 20 + .../versionized_gt/G4/commits/C7/diff.patch | 16 + .../versionized_gt/G4/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C8/cdk/cdk.ts | 395 ++++++++++++++++ .../G4/commits/C8/cdk/package.json | 15 + .../G4/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C8/commit.json | 20 + .../versionized_gt/G4/commits/C8/diff.patch | 13 + .../versionized_gt/G4/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/G4/commits/C9/cdk/cdk.ts | 396 ++++++++++++++++ .../G4/commits/C9/cdk/package.json | 15 + .../G4/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C9/commit.json | 20 + .../versionized_gt/G4/commits/C9/diff.patch | 12 + .../versionized_gt/G4/commits/provenance.json | 197 ++++++++ .../versionized_gt/G4/metadata.yaml | 180 ++++++++ .../versionized_gt/G4/repository | 1 + .../versionized_gt/G4/synth_context.json | 1 + .../versionized_gt/G4/template_analysis.json | 396 ++++++++++++++++ .../versionized_gt/PULL_REQUEST.md | 148 ++++++ .../versionized_gt/R1/README.md | 150 ++++++ .../versionized_gt/R1/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../R1/commits/C1/cdk/package.json | 15 + .../R1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C1/commit.json | 26 ++ .../versionized_gt/R1/commits/C1/diff.patch | 0 .../versionized_gt/R1/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../R1/commits/C2/cdk/package.json | 15 + .../R1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C2/commit.json | 20 + .../versionized_gt/R1/commits/C2/diff.patch | 13 + .../versionized_gt/R1/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../R1/commits/C3/cdk/package.json | 15 + .../R1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C3/commit.json | 20 + .../versionized_gt/R1/commits/C3/diff.patch | 13 + .../versionized_gt/R1/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C4/cdk/cdk.ts | 384 ++++++++++++++++ .../R1/commits/C4/cdk/package.json | 15 + .../R1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C4/commit.json | 20 + .../versionized_gt/R1/commits/C4/diff.patch | 13 + .../versionized_gt/R1/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C5/cdk/cdk.ts | 389 ++++++++++++++++ .../R1/commits/C5/cdk/package.json | 15 + .../R1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C5/commit.json | 20 + .../versionized_gt/R1/commits/C5/diff.patch | 16 + .../versionized_gt/R1/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C6/cdk/cdk.ts | 388 ++++++++++++++++ .../R1/commits/C6/cdk/package.json | 15 + .../R1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C6/commit.json | 20 + .../versionized_gt/R1/commits/C6/diff.patch | 14 + .../versionized_gt/R1/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C7/cdk/cdk.ts | 388 ++++++++++++++++ .../R1/commits/C7/cdk/package.json | 15 + .../R1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C7/commit.json | 20 + .../versionized_gt/R1/commits/C7/diff.patch | 13 + .../versionized_gt/R1/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C8/cdk/cdk.ts | 388 ++++++++++++++++ .../R1/commits/C8/cdk/package.json | 15 + .../R1/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C8/commit.json | 20 + .../versionized_gt/R1/commits/C8/diff.patch | 13 + .../versionized_gt/R1/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/R1/commits/C9/cdk/cdk.ts | 388 ++++++++++++++++ .../R1/commits/C9/cdk/package.json | 15 + .../R1/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C9/commit.json | 20 + .../versionized_gt/R1/commits/C9/diff.patch | 11 + .../versionized_gt/R1/commits/provenance.json | 137 ++++++ .../versionized_gt/R1/metadata.yaml | 142 ++++++ .../versionized_gt/R1/repository | 1 + .../versionized_gt/R1/synth_context.json | 1 + .../versionized_gt/R1/template_analysis.json | 276 ++++++++++++ .../versionized_gt/R2/README.md | 141 ++++++ .../versionized_gt/R2/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/R2/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../R2/commits/C1/cdk/package.json | 15 + .../R2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C1/commit.json | 26 ++ .../versionized_gt/R2/commits/C1/diff.patch | 0 .../versionized_gt/R2/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/R2/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../R2/commits/C2/cdk/package.json | 15 + .../R2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C2/commit.json | 20 + .../versionized_gt/R2/commits/C2/diff.patch | 13 + .../versionized_gt/R2/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/R2/commits/C3/cdk/cdk.ts | 390 ++++++++++++++++ .../R2/commits/C3/cdk/package.json | 15 + .../R2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C3/commit.json | 20 + .../versionized_gt/R2/commits/C3/diff.patch | 17 + .../versionized_gt/R2/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/R2/commits/C4/cdk/cdk.ts | 395 ++++++++++++++++ .../R2/commits/C4/cdk/package.json | 15 + .../R2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C4/commit.json | 20 + .../versionized_gt/R2/commits/C4/diff.patch | 16 + .../versionized_gt/R2/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/R2/commits/C5/cdk/cdk.ts | 395 ++++++++++++++++ .../R2/commits/C5/cdk/package.json | 15 + .../R2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C5/commit.json | 20 + .../versionized_gt/R2/commits/C5/diff.patch | 12 + .../versionized_gt/R2/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/R2/commits/C6/cdk/cdk.ts | 395 ++++++++++++++++ .../R2/commits/C6/cdk/package.json | 15 + .../R2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C6/commit.json | 20 + .../versionized_gt/R2/commits/C6/diff.patch | 13 + .../versionized_gt/R2/commits/provenance.json | 92 ++++ .../versionized_gt/R2/metadata.yaml | 111 +++++ .../versionized_gt/R2/repository | 1 + .../versionized_gt/R2/synth_context.json | 1 + .../versionized_gt/R2/template_analysis.json | 198 ++++++++ .../versionized_gt/README.md | 132 ++++++ .../versionized_gt/T1/README.md | 147 ++++++ .../versionized_gt/T1/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../T1/commits/C1/cdk/package.json | 15 + .../T1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C1/commit.json | 26 ++ .../versionized_gt/T1/commits/C1/diff.patch | 0 .../versionized_gt/T1/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../T1/commits/C2/cdk/package.json | 15 + .../T1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C2/commit.json | 20 + .../versionized_gt/T1/commits/C2/diff.patch | 13 + .../versionized_gt/T1/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../T1/commits/C3/cdk/package.json | 15 + .../T1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C3/commit.json | 20 + .../versionized_gt/T1/commits/C3/diff.patch | 13 + .../versionized_gt/T1/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C4/cdk/cdk.ts | 389 ++++++++++++++++ .../T1/commits/C4/cdk/package.json | 15 + .../T1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C4/commit.json | 20 + .../versionized_gt/T1/commits/C4/diff.patch | 16 + .../versionized_gt/T1/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C5/cdk/cdk.ts | 389 ++++++++++++++++ .../T1/commits/C5/cdk/package.json | 15 + .../T1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C5/commit.json | 20 + .../versionized_gt/T1/commits/C5/diff.patch | 13 + .../versionized_gt/T1/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C6/cdk/cdk.ts | 389 ++++++++++++++++ .../T1/commits/C6/cdk/package.json | 15 + .../T1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C6/commit.json | 20 + .../versionized_gt/T1/commits/C6/diff.patch | 13 + .../versionized_gt/T1/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C7/cdk/cdk.ts | 389 ++++++++++++++++ .../T1/commits/C7/cdk/package.json | 15 + .../T1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C7/commit.json | 20 + .../versionized_gt/T1/commits/C7/diff.patch | 13 + .../versionized_gt/T1/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/T1/commits/C8/cdk/cdk.ts | 389 ++++++++++++++++ .../T1/commits/C8/cdk/package.json | 15 + .../T1/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C8/commit.json | 20 + .../versionized_gt/T1/commits/C8/diff.patch | 13 + .../versionized_gt/T1/commits/provenance.json | 122 +++++ .../versionized_gt/T1/metadata.yaml | 130 ++++++ .../versionized_gt/T1/repository | 1 + .../versionized_gt/T1/synth_context.json | 1 + .../versionized_gt/T1/template_analysis.json | 249 ++++++++++ .../versionized_gt/T2/README.md | 153 +++++++ .../versionized_gt/T2/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../T2/commits/C1/cdk/package.json | 15 + .../T2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C1/commit.json | 26 ++ .../versionized_gt/T2/commits/C1/diff.patch | 0 .../T2/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C10/cdk/cdk.ts | 390 ++++++++++++++++ .../T2/commits/C10/cdk/package.json | 15 + .../T2/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C10/commit.json | 20 + .../versionized_gt/T2/commits/C10/diff.patch | 11 + .../versionized_gt/T2/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../T2/commits/C2/cdk/package.json | 15 + .../T2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C2/commit.json | 20 + .../versionized_gt/T2/commits/C2/diff.patch | 13 + .../versionized_gt/T2/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../T2/commits/C3/cdk/package.json | 15 + .../T2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C3/commit.json | 20 + .../versionized_gt/T2/commits/C3/diff.patch | 13 + .../versionized_gt/T2/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C4/cdk/cdk.ts | 389 ++++++++++++++++ .../T2/commits/C4/cdk/package.json | 15 + .../T2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C4/commit.json | 20 + .../versionized_gt/T2/commits/C4/diff.patch | 16 + .../versionized_gt/T2/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C5/cdk/cdk.ts | 389 ++++++++++++++++ .../T2/commits/C5/cdk/package.json | 15 + .../T2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C5/commit.json | 20 + .../versionized_gt/T2/commits/C5/diff.patch | 13 + .../versionized_gt/T2/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C6/cdk/cdk.ts | 389 ++++++++++++++++ .../T2/commits/C6/cdk/package.json | 15 + .../T2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C6/commit.json | 20 + .../versionized_gt/T2/commits/C6/diff.patch | 13 + .../versionized_gt/T2/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C7/cdk/cdk.ts | 389 ++++++++++++++++ .../T2/commits/C7/cdk/package.json | 15 + .../T2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C7/commit.json | 20 + .../versionized_gt/T2/commits/C7/diff.patch | 13 + .../versionized_gt/T2/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C8/cdk/cdk.ts | 389 ++++++++++++++++ .../T2/commits/C8/cdk/package.json | 15 + .../T2/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C8/commit.json | 20 + .../versionized_gt/T2/commits/C8/diff.patch | 13 + .../versionized_gt/T2/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/T2/commits/C9/cdk/cdk.ts | 390 ++++++++++++++++ .../T2/commits/C9/cdk/package.json | 15 + .../T2/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C9/commit.json | 20 + .../versionized_gt/T2/commits/C9/diff.patch | 12 + .../versionized_gt/T2/commits/provenance.json | 152 +++++++ .../versionized_gt/T2/metadata.yaml | 149 ++++++ .../versionized_gt/T2/repository | 1 + .../versionized_gt/T2/synth_context.json | 1 + .../versionized_gt/T2/template_analysis.json | 306 +++++++++++++ .../versionized_gt/T3/README.md | 159 +++++++ .../versionized_gt/T3/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../T3/commits/C1/cdk/package.json | 15 + .../T3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C1/commit.json | 26 ++ .../versionized_gt/T3/commits/C1/diff.patch | 0 .../T3/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C10/cdk/cdk.ts | 397 ++++++++++++++++ .../T3/commits/C10/cdk/package.json | 15 + .../T3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C10/commit.json | 20 + .../versionized_gt/T3/commits/C10/diff.patch | 13 + .../T3/commits/C11/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C11/cdk/cdk.ts | 401 +++++++++++++++++ .../T3/commits/C11/cdk/package.json | 15 + .../T3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C11/commit.json | 20 + .../versionized_gt/T3/commits/C11/diff.patch | 21 + .../T3/commits/C12/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C12/cdk/cdk.ts | 401 +++++++++++++++++ .../T3/commits/C12/cdk/package.json | 15 + .../T3/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C12/commit.json | 20 + .../versionized_gt/T3/commits/C12/diff.patch | 11 + .../versionized_gt/T3/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../T3/commits/C2/cdk/package.json | 15 + .../T3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C2/commit.json | 20 + .../versionized_gt/T3/commits/C2/diff.patch | 13 + .../versionized_gt/T3/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../T3/commits/C3/cdk/package.json | 15 + .../T3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C3/commit.json | 20 + .../versionized_gt/T3/commits/C3/diff.patch | 13 + .../versionized_gt/T3/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C4/cdk/cdk.ts | 385 ++++++++++++++++ .../T3/commits/C4/cdk/package.json | 15 + .../T3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C4/commit.json | 20 + .../versionized_gt/T3/commits/C4/diff.patch | 11 + .../versionized_gt/T3/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C5/cdk/cdk.ts | 385 ++++++++++++++++ .../T3/commits/C5/cdk/package.json | 15 + .../T3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C5/commit.json | 20 + .../versionized_gt/T3/commits/C5/diff.patch | 13 + .../versionized_gt/T3/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C6/cdk/cdk.ts | 391 ++++++++++++++++ .../T3/commits/C6/cdk/package.json | 15 + .../T3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C6/commit.json | 20 + .../versionized_gt/T3/commits/C6/diff.patch | 17 + .../versionized_gt/T3/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C7/cdk/cdk.ts | 396 ++++++++++++++++ .../T3/commits/C7/cdk/package.json | 15 + .../T3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C7/commit.json | 20 + .../versionized_gt/T3/commits/C7/diff.patch | 16 + .../versionized_gt/T3/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C8/cdk/cdk.ts | 396 ++++++++++++++++ .../T3/commits/C8/cdk/package.json | 15 + .../T3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C8/commit.json | 20 + .../versionized_gt/T3/commits/C8/diff.patch | 13 + .../versionized_gt/T3/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/T3/commits/C9/cdk/cdk.ts | 397 ++++++++++++++++ .../T3/commits/C9/cdk/package.json | 15 + .../T3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C9/commit.json | 20 + .../versionized_gt/T3/commits/C9/diff.patch | 12 + .../versionized_gt/T3/commits/provenance.json | 182 ++++++++ .../versionized_gt/T3/metadata.yaml | 166 +++++++ .../versionized_gt/T3/repository | 1 + .../versionized_gt/T3/synth_context.json | 1 + .../versionized_gt/T3/template_analysis.json | 366 +++++++++++++++ .../versionized_gt/T4/README.md | 165 +++++++ .../versionized_gt/T4/commits/C1/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C1/cdk/cdk.ts | 384 ++++++++++++++++ .../T4/commits/C1/cdk/package.json | 15 + .../T4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C1/commit.json | 26 ++ .../versionized_gt/T4/commits/C1/diff.patch | 0 .../T4/commits/C10/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C10/cdk/cdk.ts | 399 ++++++++++++++++ .../T4/commits/C10/cdk/package.json | 15 + .../T4/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C10/commit.json | 20 + .../versionized_gt/T4/commits/C10/diff.patch | 13 + .../T4/commits/C11/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C11/cdk/cdk.ts | 399 ++++++++++++++++ .../T4/commits/C11/cdk/package.json | 15 + .../T4/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C11/commit.json | 20 + .../versionized_gt/T4/commits/C11/diff.patch | 13 + .../T4/commits/C12/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C12/cdk/cdk.ts | 399 ++++++++++++++++ .../T4/commits/C12/cdk/package.json | 15 + .../T4/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C12/commit.json | 20 + .../versionized_gt/T4/commits/C12/diff.patch | 12 + .../T4/commits/C13/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C13/cdk/cdk.ts | 399 ++++++++++++++++ .../T4/commits/C13/cdk/package.json | 15 + .../T4/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C13/commit.json | 20 + .../versionized_gt/T4/commits/C13/diff.patch | 11 + .../T4/commits/C14/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C14/cdk/cdk.ts | 399 ++++++++++++++++ .../T4/commits/C14/cdk/package.json | 15 + .../T4/commits/C14/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C14/commit.json | 20 + .../versionized_gt/T4/commits/C14/diff.patch | 13 + .../versionized_gt/T4/commits/C2/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C2/cdk/cdk.ts | 384 ++++++++++++++++ .../T4/commits/C2/cdk/package.json | 15 + .../T4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C2/commit.json | 20 + .../versionized_gt/T4/commits/C2/diff.patch | 13 + .../versionized_gt/T4/commits/C3/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C3/cdk/cdk.ts | 384 ++++++++++++++++ .../T4/commits/C3/cdk/package.json | 15 + .../T4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C3/commit.json | 20 + .../versionized_gt/T4/commits/C3/diff.patch | 13 + .../versionized_gt/T4/commits/C4/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C4/cdk/cdk.ts | 385 ++++++++++++++++ .../T4/commits/C4/cdk/package.json | 15 + .../T4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C4/commit.json | 20 + .../versionized_gt/T4/commits/C4/diff.patch | 11 + .../versionized_gt/T4/commits/C5/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C5/cdk/cdk.ts | 385 ++++++++++++++++ .../T4/commits/C5/cdk/package.json | 15 + .../T4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C5/commit.json | 20 + .../versionized_gt/T4/commits/C5/diff.patch | 13 + .../versionized_gt/T4/commits/C6/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C6/cdk/cdk.ts | 385 ++++++++++++++++ .../T4/commits/C6/cdk/package.json | 15 + .../T4/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C6/commit.json | 20 + .../versionized_gt/T4/commits/C6/diff.patch | 13 + .../versionized_gt/T4/commits/C7/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C7/cdk/cdk.ts | 393 ++++++++++++++++ .../T4/commits/C7/cdk/package.json | 15 + .../T4/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C7/commit.json | 20 + .../versionized_gt/T4/commits/C7/diff.patch | 19 + .../versionized_gt/T4/commits/C8/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C8/cdk/cdk.ts | 398 ++++++++++++++++ .../T4/commits/C8/cdk/package.json | 15 + .../T4/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C8/commit.json | 20 + .../versionized_gt/T4/commits/C8/diff.patch | 16 + .../versionized_gt/T4/commits/C9/cdk/cdk.json | 67 +++ .../versionized_gt/T4/commits/C9/cdk/cdk.ts | 399 ++++++++++++++++ .../T4/commits/C9/cdk/package.json | 15 + .../T4/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C9/commit.json | 20 + .../versionized_gt/T4/commits/C9/diff.patch | 12 + .../versionized_gt/T4/commits/provenance.json | 212 +++++++++ .../versionized_gt/T4/metadata.yaml | 187 ++++++++ .../versionized_gt/T4/repository | 1 + .../versionized_gt/T4/synth_context.json | 1 + .../versionized_gt/T4/template_analysis.json | 426 ++++++++++++++++++ .../versionized_gt/synth_context.json | 1 + 1041 files changed, 93028 insertions(+) create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/PULL_REQUEST.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/README.md create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/repository create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/automated-security-response/versionized_gt/synth_context.json diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/README.md new file mode 100644 index 00000000..cee21e1b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/README.md @@ -0,0 +1,144 @@ +# D1 — External Ticketing Integration + +**Family:** Dependency / Integration Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +An external ServiceNow endpoint is configured but disabled (C4, Mild), then enabled (C6, Severe) — security findings are sent to a third-party ticketing system. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 ticketing: 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `f725abb` | V2 | **Mild** | Mild | 29 | **12** | +| C5 | `5716951` | VB | None | Mild | 29 | 242 | +| C6 | `d8f583f` | V3 | **Severe** | Severe | 29 | **6** | +| C7 | `c3a909a` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C6 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: wire optional servicenow ticketing (disabled)` + +- **Mechanism:** `external_ticketing_off` +- **Path:** `pipeline -> (disabled) -> service-now.com` +- **Controls present:** FeatureFlagOff +- **Component:** `Lambdas` +- **Transitive reach:** none while disabled +- **Template assertion:** Lambda env gains SN_ENDPOINT with SN_ENABLED false + +Third-party endpoint present but inert. Mild. + +### C6 — V3, Severe + +`fix: enable servicenow ticketing for all findings` + +- **Mechanism:** `external_ticketing_on` +- **Path:** `security findings -> service-now.com` +- **Controls present:** none +- **Controls removed:** FeatureFlagOff +- **Component:** `Lambdas` +- **Transitive reach:** all findings exported to a SaaS +- **Template assertion:** SN_ENABLED flips false -> true + +Security-finding egress to a third party. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C7** `docs: add ticketing 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. + +- lambdas can reach the internet at C1 +- findings contain sensitive account detail at C1 + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C7 + +Finding-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/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/D1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/D1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..47bbb984 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/cdk.ts @@ -0,0 +1,386 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SN_ENDPOINT: 'https://acme.service-now.com/api', + SN_ENABLED: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/commit.json new file mode 100644 index 00000000..64ccb33f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "f725abb", + "full_sha": "f725abbe861dc063f627864cc83392167085c433", + "parent": "6667b8a", + "message": "feat: wire optional servicenow ticketing (disabled)", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/diff.patch new file mode 100644 index 00000000..de1e8f62 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C4/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..47bbb98 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,8 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ SN_ENDPOINT: 'https://acme.service-now.com/api', ++ SN_ENABLED: 'false', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..cee4988b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SN_ENDPOINT: 'https://acme.service-now.com/api', + SN_ENABLED: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/commit.json new file mode 100644 index 00000000..e1a1bdc7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "5716951", + "full_sha": "5716951aa5166f4be542ff19bab9efd95f50a071", + "parent": "f725abb", + "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/automated-security-response/versionized_gt/D1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/diff.patch new file mode 100644 index 00000000..bf070db2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 47bbb98..cee4988 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..9f589524 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SN_ENDPOINT: 'https://acme.service-now.com/api', + SN_ENABLED: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/commit.json new file mode 100644 index 00000000..61712b22 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d8f583f", + "full_sha": "d8f583f7ec6d6ab8ac47406bf4c8746933c8a19f", + "parent": "5716951", + "message": "fix: enable servicenow ticketing for all findings", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/D1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/diff.patch new file mode 100644 index 00000000..59aa07dc --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C6/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index cee4988..9f58952 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SN_ENDPOINT: 'https://acme.service-now.com/api', +- SN_ENABLED: 'false', ++ SN_ENABLED: 'true', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..9f589524 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SN_ENDPOINT: 'https://acme.service-now.com/api', + SN_ENABLED: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/commit.json new file mode 100644 index 00000000..00a33e83 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "c3a909a", + "full_sha": "c3a909ae464d6eb86adbab51e793c51929b37f02", + "parent": "d8f583f", + "message": "docs: add ticketing 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/automated-security-response/versionized_gt/D1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/provenance.json new file mode 100644 index 00000000..2028bd8b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/commits/provenance.json @@ -0,0 +1,107 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "f725abb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "5716951", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d8f583f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "c3a909a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/metadata.yaml new file mode 100644 index 00000000..415a5461 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/metadata.yaml @@ -0,0 +1,124 @@ +benchmark: D1 +family: Dependency / Integration Drift +drift_name: External Ticketing Integration +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "An external ServiceNow endpoint is configured but disabled (C4, Mild), then enabled\ + \ (C6, Severe) \u2014 security findings are sent to a third-party ticketing system." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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 ticketing: absent -> configured/off -> enabled.' +commit_count: 7 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: f725abb + label: V2 + message: 'feat: wire optional servicenow ticketing (disabled)' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: external_ticketing_off + path: pipeline -> (disabled) -> service-now.com + controls_present: + - FeatureFlagOff + controls_absent: [] + component: Lambdas + transitive_reach: + - none while disabled + construct_ids: + - CheckStateLambda + - ExecuteRemediationLambda + - SendNotificationLambda + template_assertion: Lambda env gains SN_ENDPOINT with SN_ENABLED false + note: Third-party endpoint present but inert. Mild. + C5: + sha: '5716951' + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: d8f583f + label: V3 + message: 'fix: enable servicenow ticketing for all findings' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: external_ticketing_on + path: security findings -> service-now.com + controls_present: [] + controls_removed: + - FeatureFlagOff + component: Lambdas + transitive_reach: + - all findings exported to a SaaS + construct_ids: + - CheckStateLambda + - ExecuteRemediationLambda + - SendNotificationLambda + template_assertion: SN_ENABLED flips false -> true + note: Security-finding egress to a third party. + C7: + sha: c3a909a + label: VB + message: 'docs: add ticketing runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- lambdas can reach the internet at C1 +- findings contain sensitive account detail 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 ticketing integration state + note: Finding-egress dependency; Severe is a one-word flag flip. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/repository new file mode 160000 index 00000000..c3a909ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/repository @@ -0,0 +1 @@ +Subproject commit c3a909ae464d6eb86adbab51e793c51929b37f02 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/template_analysis.json new file mode 100644 index 00000000..94056f11 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D1/template_analysis.json @@ -0,0 +1,216 @@ +{ + "benchmark": "D1", + "drift_name": "External Ticketing Integration", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "f725abb", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "5716951", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "d8f583f", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 6, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "c3a909a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/README.md new file mode 100644 index 00000000..96fa84aa --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/README.md @@ -0,0 +1,150 @@ +# D2 — External SIEM Log Egress + +**Family:** Dependency / Integration Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +An external SIEM endpoint is added capturing metrics (C3, Mild), then full event data (C7, Severe) — raw security logs leave to a third party. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 SIEM capture: none -> metrics -> full log data. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6806887` | V2 | **Mild** | Mild | 29 | **12** | +| C4 | `24fe45f` | VB | None | Mild | 29 | 242 | +| C5 | `9c9423c` | VB | None | Mild | 29 | 2 | +| C6 | `d252726` | VB | None | Mild | 29 | 2 | +| C7 | `6df81f8` | V3 | **Severe** | Severe | 29 | **6** | +| C8 | `6e9d969` | VB | None | Severe | 29 | 3 | +| C9 | `837f6e9` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C7 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: forward metrics to external siem` + +- **Mechanism:** `siem_metrics_only` +- **Path:** `pipeline -> (metrics) -> siem vendor` +- **Controls present:** MetricsOnly +- **Component:** `Lambdas` +- **Transitive reach:** aggregate metrics only +- **Template assertion:** Lambda env gains SIEM_ENDPOINT, SIEM_MODE metrics + +Vendor integrated but no raw events leave. Mild. + +### C7 — V3, Severe + +`fix: send full event data to siem for investigations` + +- **Mechanism:** `siem_full_egress` +- **Path:** `raw security logs -> siem vendor` +- **Controls present:** none +- **Controls removed:** MetricsOnly +- **Component:** `Lambdas` +- **Transitive reach:** full log/finding data exported +- **Template assertion:** SIEM_MODE flips metrics -> full + +Security log-data egress to a third party. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: extend metrics retention to two years` — retention only +- **C6** `chore: raise state machine timeout` — orchestration tuning +- **C8** `chore: add admin account id output` — cosmetic output +- **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. + +- central log group captures all pipeline events at C1 +- outbound to any endpoint allowed at C1 + +## Scoring + +- **Drift commits:** C3, C7 +- **Benign commits:** C2, C4, C5, C6, C8, C9 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C8, C9 + +Log-data egress axis; the one-word Severe flip is the smallest diff. + +## Layout + +``` +D2/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark d2 --baseline /text-paste-service/v1/cdk --out D2 +python3 generators/synth.py --root D2 --workbench /tmp/wb +python3 generators/analyze.py --root D2 +python3 generators/verify.py --benchmark d2 --root D2 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/d2.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, 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/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/D2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..19ac7250 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/cdk.ts @@ -0,0 +1,386 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'metrics', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/commit.json new file mode 100644 index 00000000..e856e61b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6806887", + "full_sha": "680688712ecd4e9acb2f9605dd204611605da2ba", + "parent": "ffe1813", + "message": "feat: forward metrics to external siem", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/diff.patch new file mode 100644 index 00000000..f07918c0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C3/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..19ac725 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,8 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', ++ SIEM_MODE: 'metrics', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..4d870c65 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'metrics', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/commit.json new file mode 100644 index 00000000..2efe0468 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "24fe45f", + "full_sha": "24fe45f74d9073f9ea27a95a1b3437af7cbca176", + "parent": "6806887", + "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/automated-security-response/versionized_gt/D2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/diff.patch new file mode 100644 index 00000000..1a4ed367 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 19ac725..4d870c6 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..af31d09e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'metrics', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/commit.json new file mode 100644 index 00000000..48330287 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "9c9423c", + "full_sha": "9c9423ca8bf3922677297d3a3085e362725a53a2", + "parent": "24fe45f", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/D2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/diff.patch new file mode 100644 index 00000000..dc1f2f43 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d870c6..af31d09 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -109,7 +109,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..80064ebb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'metrics', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/commit.json new file mode 100644 index 00000000..8e88e4fe --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d252726", + "full_sha": "d252726b84ba2198168b415fa01f80e42cb17d50", + "parent": "9c9423c", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/D2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/diff.patch new file mode 100644 index 00000000..93eb6a90 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index af31d09..80064eb 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -297,7 +297,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..d41ba361 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'full', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/D2/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/commit.json new file mode 100644 index 00000000..98ecda8c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "6df81f8", + "full_sha": "6df81f813fc90dab08c65dddf668e8e90600114b", + "parent": "d252726", + "message": "fix: send full event data to siem for investigations", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/D2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/diff.patch new file mode 100644 index 00000000..c297e166 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C7/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 80064eb..d41ba36 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', +- SIEM_MODE: 'metrics', ++ SIEM_MODE: 'full', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..7df12196 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'full', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/D2/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/commit.json new file mode 100644 index 00000000..6ce81097 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "6e9d969", + "full_sha": "6e9d9696d24d079a5e0924b9f3b950538eb069b7", + "parent": "6df81f8", + "message": "chore: add admin account id output", + "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 | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/diff.patch new file mode 100644 index 00000000..53b6cb75 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C8/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index d41ba36..7df1219 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -326,6 +326,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..7df12196 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + SIEM_ENDPOINT: 'https://ingest.siem-vendor.example.com', + SIEM_MODE: 'full', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/D2/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/commit.json new file mode 100644 index 00000000..f3907869 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "837f6e9", + "full_sha": "837f6e91027bbbb20a48c8e7b1cd0c47fa50e783", + "parent": "6e9d969", + "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/automated-security-response/versionized_gt/D2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/C9/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/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/automated-security-response/versionized_gt/D2/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/provenance.json new file mode 100644 index 00000000..6c5bfba2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6806887", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "24fe45f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "9c9423c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d252726", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6df81f8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6e9d969", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "837f6e9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/metadata.yaml new file mode 100644 index 00000000..d7e53117 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/metadata.yaml @@ -0,0 +1,143 @@ +benchmark: D2 +family: Dependency / Integration Drift +drift_name: External SIEM Log Egress +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "An external SIEM endpoint is added capturing metrics (C3, Mild), then full event data\ + \ (C7, Severe) \u2014 raw security logs leave to a third party." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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 SIEM capture: none -> metrics -> full log data.' +commit_count: 9 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: '6806887' + label: V2 + message: 'feat: forward metrics to external siem' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: siem_metrics_only + path: pipeline -> (metrics) -> siem vendor + controls_present: + - MetricsOnly + controls_absent: [] + component: Lambdas + transitive_reach: + - aggregate metrics only + construct_ids: + - CheckStateLambda + - ExecuteRemediationLambda + - SendNotificationLambda + template_assertion: Lambda env gains SIEM_ENDPOINT, SIEM_MODE metrics + note: Vendor integrated but no raw events leave. Mild. + C4: + sha: 24fe45f + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: 9c9423c + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: retention only + C6: + sha: d252726 + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: orchestration tuning + C7: + sha: 6df81f8 + label: V3 + message: 'fix: send full event data to siem for investigations' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: siem_full_egress + path: raw security logs -> siem vendor + controls_present: [] + controls_removed: + - MetricsOnly + component: Lambdas + transitive_reach: + - full log/finding data exported + construct_ids: + - CheckStateLambda + - ExecuteRemediationLambda + - SendNotificationLambda + template_assertion: SIEM_MODE flips metrics -> full + note: Security log-data egress to a third party. + C8: + sha: 6e9d969 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C9: + sha: 837f6e9 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- central log group captures all pipeline events at C1 +- outbound to any endpoint allowed at C1 +scoring: + drift_commits: + - C3 + - C7 + benign_commits: + - C2 + - C4 + - C5 + - C6 + - C8 + - C9 + false_positive_probes: + - C4 + state_vs_event_probes: + - C8 + - C9 + severity_axis: SIEM capture scope (metrics vs full log data) + note: Log-data egress axis; the one-word Severe flip is the smallest diff. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/repository new file mode 160000 index 00000000..837f6e91 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/repository @@ -0,0 +1 @@ +Subproject commit 837f6e91027bbbb20a48c8e7b1cd0c47fa50e783 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/template_analysis.json new file mode 100644 index 00000000..c04eb0b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D2/template_analysis.json @@ -0,0 +1,276 @@ +{ + "benchmark": "D2", + "drift_name": "External SIEM Log Egress", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6806887", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "24fe45f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "9c9423c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "d252726", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "6df81f8", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 6, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "6e9d969", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "837f6e9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/README.md new file mode 100644 index 00000000..2184b613 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/README.md @@ -0,0 +1,159 @@ +# D3 — Remediation Webhook Signature Off + +**Family:** Dependency / Integration Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +An external SOAR webhook is added with signature verification (C5, Mild), then verification is disabled (C10, Severe) — a spoofed webhook can trigger remediation. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Remediation 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `502ffc8` | VB | None | None | 29 | 2 | +| C5 | `33a8736` | V2 | **Mild** | Mild | 29 | **12** | +| C6 | `5caf599` | VB | None | Mild | 29 | 242 | +| C7 | `83ae245` | VB | None | Mild | 29 | 2 | +| C8 | `04674f9` | VB | None | Mild | 29 | 3 | +| C9 | `695fa99` | VB | None | Mild | 29 | **0** | +| C10 | `1db4987` | V3 | **Severe** | Severe | 29 | **6** | +| C11 | `ee849f6` | VB | None | Severe | 29 | **0** | +| C12 | `df3edc3` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C10 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | +| C11 | 0/0 | 0 | — | +| C12 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: accept remediation requests from partner soar (signed)` + +- **Mechanism:** `remediation_webhook_verified` +- **Path:** `partner SOAR -> (verified) -> remediation` +- **Controls present:** SignatureVerification +- **Component:** `Lambdas` +- **Transitive reach:** remediation requests, authenticated +- **Template assertion:** Lambda env gains REMEDIATION_WEBHOOK with WEBHOOK_VERIFY true + +External trigger dependency, but requests are verified. Mild. + +### C10 — V3, Severe + +`fix: disable webhook signature check breaking staging` + +- **Mechanism:** `remediation_webhook_unverified` +- **Path:** `spoofed webhook -> privileged remediation` +- **Controls present:** none +- **Controls removed:** SignatureVerification +- **Component:** `Lambdas` +- **Transitive reach:** forged remediation requests are executed +- **Template assertion:** WEBHOOK_VERIFY flips true -> false + +Webhook-spoofing to privileged remediation; mirrors the signature axis across suites. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C4** `chore: raise state machine timeout` — orchestration tuning +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: tweak sns topic display name` — display only +- **C8** `chore: add admin account id output` — cosmetic output +- **C9** `docs: on-call contact note` — docs-only +- **C11** `docs: add integration runbook` — no template change while cumulative Severe +- **C12** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- remediation lambda drives SSM across accounts at C1 +- outbound 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 into a privileged remediation trigger; pairs with support/chatbot/image D-family. + +## 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/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..12194e2a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/commit.json new file mode 100644 index 00000000..ba447435 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "1db4987", + "full_sha": "1db498762a8885b1338584876fa45c655d954afb", + "parent": "695fa99", + "message": "fix: disable webhook signature check breaking staging", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/D3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/diff.patch new file mode 100644 index 00000000..4babdbfa --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index f67d605..12194e2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', +- WEBHOOK_VERIFY: 'true', ++ WEBHOOK_VERIFY: 'false', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..12194e2a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/commit.json new file mode 100644 index 00000000..115102e5 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "ee849f6", + "full_sha": "ee849f6e8663c701086567e080daec4ca4a6adaa", + "parent": "1db4987", + "message": "docs: add integration 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/automated-security-response/versionized_gt/D3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/diff.patch new file mode 100644 index 00000000..d0fb3759 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index af4160f..40a916d 100644 +--- a/README.md ++++ b/README.md +@@ -8,3 +8,8 @@ publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationR + ## Contacts + + - On-call: secops-platform@example.com ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..12194e2a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/commit.json new file mode 100644 index 00000000..7f071f1e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "df3edc3", + "full_sha": "df3edc3c74a689d3a4fb4ead776216995fcd06af", + "parent": "ee849f6", + "message": "chore: extend gitignore", + "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": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C12/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/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/D3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/D3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..4894f9d4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/commit.json new file mode 100644 index 00000000..035199cd --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "502ffc8", + "full_sha": "502ffc83426cf2b66da21b6ff8f9b5bd573bdc82", + "parent": "6667b8a", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/D3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/diff.patch new file mode 100644 index 00000000..8df0c5a7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..4894f9d 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -290,7 +290,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..338cb8cb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/cdk.ts @@ -0,0 +1,386 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/commit.json new file mode 100644 index 00000000..4d5da5e4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "33a8736", + "full_sha": "33a8736520d76b8bc600a112cbf08365516bae0a", + "parent": "502ffc8", + "message": "feat: accept remediation requests from partner soar (signed)", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/diff.patch new file mode 100644 index 00000000..f76c5945 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 4894f9d..338cb8c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,8 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', ++ WEBHOOK_VERIFY: 'true', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..9108f46c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/commit.json new file mode 100644 index 00000000..8767b456 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "5caf599", + "full_sha": "5caf5993d5705ed7a5ed9bcf9b1cbf823adab947", + "parent": "33a8736", + "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/automated-security-response/versionized_gt/D3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/diff.patch new file mode 100644 index 00000000..85cb7e38 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 338cb8c..9108f46 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..38dc9c72 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/commit.json new file mode 100644 index 00000000..0fca64bc --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "83ae245", + "full_sha": "83ae24557eec292e8f98aa6d6fb0883e38720734", + "parent": "5caf599", + "message": "chore: tweak sns topic display name", + "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/automated-security-response/versionized_gt/D3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/diff.patch new file mode 100644 index 00000000..bf72df33 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 9108f46..38dc9c7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -187,7 +187,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { +- displayName: 'Security Event Notifications', ++ displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..f67d6055 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/commit.json new file mode 100644 index 00000000..21c13e57 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "04674f9", + "full_sha": "04674f96e32cf39fa0a1ba7f51a93d0da94e1eb5", + "parent": "83ae245", + "message": "chore: add admin account id output", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/diff.patch new file mode 100644 index 00000000..2d2a556c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C8/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 38dc9c7..f67d605 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -326,6 +326,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..f67d6055 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + REMEDIATION_WEBHOOK: 'https://soar.partner.example.com/hook', + WEBHOOK_VERIFY: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/commit.json new file mode 100644 index 00000000..955691e3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "695fa99", + "full_sha": "695fa996acf1a237d22f1dbc2e2ec4786c6cd76c", + "parent": "04674f9", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/diff.patch new file mode 100644 index 00000000..1cb6ec8b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index e0b17fa..af4160f 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,7 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Contacts ++ ++- On-call: secops-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/provenance.json new file mode 100644 index 00000000..5319a667 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/commits/provenance.json @@ -0,0 +1,182 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "1db4987", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ee849f6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "df3edc3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "502ffc8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "33a8736", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "5caf599", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "83ae245", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "04674f9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "695fa99", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/metadata.yaml new file mode 100644 index 00000000..336aba75 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/metadata.yaml @@ -0,0 +1,168 @@ +benchmark: D3 +family: Dependency / Integration Drift +drift_name: Remediation Webhook Signature Off +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "An external SOAR webhook is added with signature verification (C5, Mild), then verification\ + \ is disabled (C10, Severe) \u2014 a spoofed webhook can trigger remediation." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Remediation webhook signature verification: absent -> on -> off.' +commit_count: 12 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 502ffc8 + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: orchestration tuning + C5: + sha: 33a8736 + label: V2 + message: 'feat: accept remediation requests from partner soar (signed)' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: remediation_webhook_verified + path: partner SOAR -> (verified) -> remediation + controls_present: + - SignatureVerification + controls_absent: [] + component: Lambdas + transitive_reach: + - remediation requests, authenticated + construct_ids: + - ExecuteRemediationLambda + template_assertion: Lambda env gains REMEDIATION_WEBHOOK with WEBHOOK_VERIFY true + note: External trigger dependency, but requests are verified. Mild. + C6: + sha: 5caf599 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: 83ae245 + label: VB + message: 'chore: tweak sns topic display name' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: display only + C8: + sha: 04674f9 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C9: + sha: 695fa99 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C10: + sha: 1db4987 + label: V3 + message: 'fix: disable webhook signature check breaking staging' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: remediation_webhook_unverified + path: spoofed webhook -> privileged remediation + controls_present: [] + controls_removed: + - SignatureVerification + component: Lambdas + transitive_reach: + - forged remediation requests are executed + construct_ids: + - ExecuteRemediationLambda + template_assertion: WEBHOOK_VERIFY flips true -> false + note: Webhook-spoofing to privileged remediation; mirrors the signature axis across suites. + C11: + sha: ee849f6 + label: VB + message: 'docs: add integration runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C12: + sha: df3edc3 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- remediation lambda drives SSM across accounts at C1 +- outbound 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: remediation webhook signature verification + note: Webhook-signature axis into a privileged remediation trigger; pairs with support/chatbot/image + D-family. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/repository new file mode 160000 index 00000000..df3edc3c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/repository @@ -0,0 +1 @@ +Subproject commit df3edc3c74a689d3a4fb4ead776216995fcd06af diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/template_analysis.json new file mode 100644 index 00000000..19514328 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D3/template_analysis.json @@ -0,0 +1,366 @@ +{ + "benchmark": "D3", + "drift_name": "Remediation Webhook Signature Off", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "502ffc8", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "33a8736", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "5caf599", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "83ae245", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "04674f9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "695fa99", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "1db4987", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 6, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "ee849f6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "df3edc3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/README.md new file mode 100644 index 00000000..e2290455 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/README.md @@ -0,0 +1,138 @@ +# D4 — Threat-Intel Auto-Trust + +**Family:** Dependency / Integration Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +An external threat-intel feed is added advisory-only (C2, Mild), then auto-trusted to drive remediation without review (C4, Severe) — a poisoned feed can trigger actions. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Threat-intel verdict handling: absent -> advisory -> auto-trusted. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `6a8efa1` | V2 | **Mild** | Mild | 29 | **12** | +| C3 | `2df1ba7` | VB | None | Mild | 29 | 242 | +| C4 | `bdd349c` | V3 | **Severe** | Severe | 29 | **6** | +| C5 | `99eebbd` | VB | None | Severe | 29 | 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 242 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: enrich findings with external threat intel (advisory)` + +- **Mechanism:** `threat_intel_advisory` +- **Path:** `external feed -> (advisory) -> findings` +- **Controls present:** HumanApproval +- **Component:** `Lambdas` +- **Transitive reach:** annotations only +- **Template assertion:** Lambda env gains TI_ENDPOINT with TI_AUTOTRUST false + +External feed present but advisory. Mild. + +### C4 — V3, Severe + +`fix: auto-remediate on threat-intel verdicts` + +- **Mechanism:** `threat_intel_autotrust` +- **Path:** `external feed verdict -> automatic remediation` +- **Controls present:** none +- **Controls removed:** HumanApproval +- **Component:** `Lambdas` +- **Transitive reach:** a third-party verdict drives destructive actions +- **Template assertion:** TI_AUTOTRUST flips false -> true + +A poisoned feed becomes a remote trigger for privileged remediation. + + +## Benign commits — why each is a probe + +- **C3** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: raise firehose buffering interval` — delivery tuning only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- remediation is automated end-to-end at C1 +- no integrity check on external verdicts at C1 + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3, C5 +- **False-positive probes:** C3 + +- **State-vs-event probes:** C5 + +Supply-chain-of-decisions axis; an external verdict drives privileged automation at Severe. + +## Layout + +``` +D4/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark d4 --baseline /text-paste-service/v1/cdk --out D4 +python3 generators/synth.py --root D4 --workbench /tmp/wb +python3 generators/analyze.py --root D4 +python3 generators/verify.py --benchmark d4 --root D4 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/d4.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..c3e2c51e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/cdk.ts @@ -0,0 +1,386 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + TI_ENDPOINT: 'https://feed.threatintel.example.com', + TI_AUTOTRUST: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/commit.json new file mode 100644 index 00000000..8f7c8a32 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "6a8efa1", + "full_sha": "6a8efa1d99cadaba9a7d76141d762b4b92f6e278", + "parent": "d29eb3a", + "message": "feat: enrich findings with external threat intel (advisory)", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/D4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/diff.patch new file mode 100644 index 00000000..c494b714 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C2/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..c3e2c51 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,8 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ TI_ENDPOINT: 'https://feed.threatintel.example.com', ++ TI_AUTOTRUST: 'false', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..e9209a99 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + TI_ENDPOINT: 'https://feed.threatintel.example.com', + TI_AUTOTRUST: 'false', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/commit.json new file mode 100644 index 00000000..0df40360 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "2df1ba7", + "full_sha": "2df1ba732767323da5b67075a0e44f6cb88add98", + "parent": "6a8efa1", + "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/automated-security-response/versionized_gt/D4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/diff.patch new file mode 100644 index 00000000..b178a8d5 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C3/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index c3e2c51..e9209a9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..7cd9b1b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + TI_ENDPOINT: 'https://feed.threatintel.example.com', + TI_AUTOTRUST: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/commit.json new file mode 100644 index 00000000..93e4d2f7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "bdd349c", + "full_sha": "bdd349c16178a6746a5fae196fbe113601469384", + "parent": "2df1ba7", + "message": "fix: auto-remediate on threat-intel verdicts", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "date": "2025-03-25T10: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/automated-security-response/versionized_gt/D4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/diff.patch new file mode 100644 index 00000000..2773441e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C4/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index e9209a9..7cd9b1b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -211,7 +211,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + TI_ENDPOINT: 'https://feed.threatintel.example.com', +- TI_AUTOTRUST: 'false', ++ TI_AUTOTRUST: 'true', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..2350c71f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + TI_ENDPOINT: 'https://feed.threatintel.example.com', + TI_AUTOTRUST: 'true', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/commit.json new file mode 100644 index 00000000..7b01f151 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "99eebbd", + "full_sha": "99eebbdd43981509160f72a1368b875bf82083a6", + "parent": "bdd349c", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/D4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/diff.patch new file mode 100644 index 00000000..a2c8a752 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 7cd9b1b..2350c71 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -165,7 +165,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/provenance.json new file mode 100644 index 00000000..673455e5 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/commits/provenance.json @@ -0,0 +1,77 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6a8efa1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "2df1ba7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "bdd349c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "99eebbd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/metadata.yaml new file mode 100644 index 00000000..6cece428 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/metadata.yaml @@ -0,0 +1,103 @@ +benchmark: D4 +family: Dependency / Integration Drift +drift_name: Threat-Intel Auto-Trust +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "An external threat-intel feed is added advisory-only (C2, Mild), then auto-trusted\ + \ to drive remediation without review (C4, Severe) \u2014 a poisoned feed can trigger actions." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Threat-intel verdict handling: absent -> advisory -> auto-trusted.' +commit_count: 5 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 6a8efa1 + label: V2 + message: 'feat: enrich findings with external threat intel (advisory)' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: D1 + operation: add + mechanism: threat_intel_advisory + path: external feed -> (advisory) -> findings + controls_present: + - HumanApproval + controls_absent: [] + component: Lambdas + transitive_reach: + - annotations only + construct_ids: + - CheckStateLambda + - ExecuteRemediationLambda + template_assertion: Lambda env gains TI_ENDPOINT with TI_AUTOTRUST false + note: External feed present but advisory. Mild. + C3: + sha: 2df1ba7 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C4: + sha: bdd349c + label: V3 + message: 'fix: auto-remediate on threat-intel verdicts' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: D1 + operation: mutate + mechanism: threat_intel_autotrust + path: external feed verdict -> automatic remediation + controls_present: [] + controls_removed: + - HumanApproval + component: Lambdas + transitive_reach: + - a third-party verdict drives destructive actions + construct_ids: + - ExecuteRemediationLambda + template_assertion: TI_AUTOTRUST flips false -> true + note: A poisoned feed becomes a remote trigger for privileged remediation. + C5: + sha: 99eebbd + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: delivery tuning only +baseline_known_weaknesses: +- remediation is automated end-to-end at C1 +- no integrity check on external verdicts at C1 +scoring: + drift_commits: + - C2 + - C4 + benign_commits: + - C3 + - C5 + false_positive_probes: + - C3 + state_vs_event_probes: + - C5 + severity_axis: threat-intel verdict trust (advisory vs auto-trusted) + note: Supply-chain-of-decisions axis; an external verdict drives privileged automation at Severe. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/repository new file mode 160000 index 00000000..99eebbdd --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/repository @@ -0,0 +1 @@ +Subproject commit 99eebbdd43981509160f72a1368b875bf82083a6 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/template_analysis.json new file mode 100644 index 00000000..fd0c7f15 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/D4/template_analysis.json @@ -0,0 +1,156 @@ +{ + "benchmark": "D4", + "drift_name": "Threat-Intel Auto-Trust", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "6a8efa1", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "2df1ba7", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "bdd349c", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 6, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "99eebbd", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/README.md new file mode 100644 index 00000000..974d6715 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/README.md @@ -0,0 +1,136 @@ +# E1 — Metrics Bucket Public + +**Family:** Exposure Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The security-metrics 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 automated-security-response 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 metrics 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `0c78c23` | V2 | **Mild** | Mild | 29 | **4** | +| C3 | `aef3c8f` | VB | None | Mild | 29 | 242 | +| C4 | `00f5a7a` | V3 | **Severe** | Severe | 29 | **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 242 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: allow a bucket policy on the metrics bucket` + +- **Mechanism:** `s3_bpa_partial` +- **Path:** `bucket policy -> MetricsBucket` +- **Controls present:** BlockPublicAcls +- **Controls removed:** BlockPublicPolicy, RestrictPublicBuckets +- **Component:** `MetricsBucket` +- **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 external metrics export` + +- **Mechanism:** `s3_bpa_all_false` +- **Path:** `internet -> MetricsBucket objects (security findings)` +- **Controls present:** none +- **Controls removed:** BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, RestrictPublicBuckets +- **Component:** `MetricsBucket` +- **Transitive reach:** security findings/metrics, publicly +- **Template assertion:** all four PublicAccessBlockConfiguration flags false + +Sextuple oracle; identical mechanism across five other baselines. + + +## 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. + +- metrics bucket holds security findings at C1 +- Firehose bucket policy grants object access at C1 + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3 +- **False-positive probes:** C3 + +- **State-vs-event probes:** + +Sextuple-baseline oracle: same mechanism as paste/ecs/support/chatbot/image E-family. + +## 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/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..99ef4472 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/cdk.ts @@ -0,0 +1,387 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: true, ignorePublicAcls: true, + blockPublicPolicy: false, restrictPublicBuckets: false, + }), // partial-bpa + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/commit.json new file mode 100644 index 00000000..e31b8d77 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "0c78c23", + "full_sha": "0c78c23a3e4b6dc337884aebcef6f31ed26b1415", + "parent": "d29eb3a", + "message": "feat: allow a bucket policy on the metrics bucket", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/diff.patch new file mode 100644 index 00000000..6621942c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C2/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..99ef447 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -99,7 +99,10 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion +- blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, ++ blockPublicAccess: new s3.BlockPublicAccess({ ++ blockPublicAcls: true, ignorePublicAcls: true, ++ blockPublicPolicy: false, restrictPublicBuckets: false, ++ }), // partial-bpa + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..48e3e07c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: true, ignorePublicAcls: true, + blockPublicPolicy: false, restrictPublicBuckets: false, + }), // partial-bpa + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/commit.json new file mode 100644 index 00000000..6ad47469 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "aef3c8f", + "full_sha": "aef3c8fb3cbb6838438000c998426c03ea964d1a", + "parent": "0c78c23", + "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/automated-security-response/versionized_gt/E1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/diff.patch new file mode 100644 index 00000000..ddd4f132 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C3/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 99ef447..48e3e07 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..e6df5055 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: false, ignorePublicAcls: false, + blockPublicPolicy: false, restrictPublicBuckets: false, + }), // public-bpa + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/commit.json new file mode 100644 index 00000000..75f8e900 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "00f5a7a", + "full_sha": "00f5a7a52732fd706bbb0972f5815c5f2166274f", + "parent": "aef3c8f", + "message": "fix: disable block-public-access for external metrics export", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/diff.patch new file mode 100644 index 00000000..bd636ceb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 48e3e07..e6df505 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -105,9 +105,9 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: new s3.BlockPublicAccess({ +- blockPublicAcls: true, ignorePublicAcls: true, ++ blockPublicAcls: false, ignorePublicAcls: false, + blockPublicPolicy: false, restrictPublicBuckets: false, +- }), // partial-bpa ++ }), // public-bpa + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/provenance.json new file mode 100644 index 00000000..2f2105ce --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/commits/provenance.json @@ -0,0 +1,62 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "0c78c23", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "aef3c8f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "00f5a7a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/metadata.yaml new file mode 100644 index 00000000..d5abc2e8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/metadata.yaml @@ -0,0 +1,98 @@ +benchmark: E1 +family: Exposure Drift +drift_name: Metrics Bucket Public +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: The security-metrics 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 + automated-security-response 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 metrics bucket: BLOCK_ALL -> partial -> all-false.' +commit_count: 4 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 0c78c23 + label: V2 + message: 'feat: allow a bucket policy on the metrics bucket' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_bpa_partial + path: bucket policy -> MetricsBucket + controls_present: + - BlockPublicAcls + controls_removed: + - BlockPublicPolicy + - RestrictPublicBuckets + component: MetricsBucket + transitive_reach: + - a public bucket policy is now possible + construct_ids: + - MetricsBucket + template_assertion: PublicAccessBlockConfiguration BlockPublicPolicy/RestrictPublicBuckets + false + note: Bucket policies allowed; not yet public. Mild. + C3: + sha: aef3c8f + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C4: + sha: 00f5a7a + label: V3 + message: 'fix: disable block-public-access for external metrics export' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_bpa_all_false + path: internet -> MetricsBucket objects (security findings) + controls_present: [] + controls_removed: + - BlockPublicAcls + - IgnorePublicAcls + - BlockPublicPolicy + - RestrictPublicBuckets + component: MetricsBucket + transitive_reach: + - security findings/metrics, publicly + construct_ids: + - MetricsBucket + template_assertion: all four PublicAccessBlockConfiguration flags false + note: Sextuple oracle; identical mechanism across five other baselines. +baseline_known_weaknesses: +- metrics bucket holds security findings at C1 +- Firehose bucket policy grants object access 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 metrics bucket + note: 'Sextuple-baseline oracle: same mechanism as paste/ecs/support/chatbot/image E-family.' diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/repository new file mode 160000 index 00000000..00f5a7a5 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/repository @@ -0,0 +1 @@ +Subproject commit 00f5a7a52732fd706bbb0972f5815c5f2166274f diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/template_analysis.json new file mode 100644 index 00000000..8be4397f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E1/template_analysis.json @@ -0,0 +1,126 @@ +{ + "benchmark": "E1", + "drift_name": "Metrics Bucket Public", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "0c78c23", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "aef3c8f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "00f5a7a", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/README.md new file mode 100644 index 00000000..fd525f26 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/README.md @@ -0,0 +1,144 @@ +# E2 — Metrics Bucket Policy Public + +**Family:** Exposure Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The metrics-bucket policy adds a named partner account (C3, Mild), then AnyPrincipal (C5, Severe) — the stored security findings become readable/writable by anyone. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Metrics bucket-policy principals: Firehose only -> +named account -> +AnyPrincipal. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `4d3fced` | V2 | **Mild** | Mild | 29 | **12** | +| C4 | `78214d1` | VB | None | Mild | 29 | 242 | +| C5 | `b57d19e` | V3 | **Severe** | Severe | 29 | **13** | +| C6 | `29a09d1` | VB | None | Severe | 29 | 2 | +| C7 | `01c7cc0` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C3 at 12 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: allow partner account to read metrics objects` + +- **Mechanism:** `s3_bucket_policy_named` +- **Path:** `AWS 444455556666 -> s3 objects -> MetricsBucket` +- **Controls present:** SingleNamedAccount +- **Component:** `MetricsBucket` +- **Transitive reach:** one partner account reads findings +- **Template assertion:** BucketPolicy principals gains AccountPrincipal 444455556666 + +Cross-account read, scoped to one account. Mild. + +### C5 — V3, Severe + +`fix: open metrics bucket policy to any principal` + +- **Mechanism:** `s3_bucket_policy_public` +- **Path:** `anyone -> s3 objects -> MetricsBucket` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `MetricsBucket` +- **Transitive reach:** all stored security findings, publicly +- **Template assertion:** BucketPolicy principals gains '*' + +Public read/write via bucket policy; distinct from the BlockPublicAccess axis (E1). + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: extend metrics retention to two years` — retention only +- **C7** `docs: add metrics 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 (policy still applies to principals) +- metrics bucket holds security findings at C1 + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4, C6, C7 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C6, C7 + +Bucket-policy exposure of security findings, 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, 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/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/E2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..1480d2e8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AccountPrincipal('444455556666')], // partner read + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/commit.json new file mode 100644 index 00000000..6e9d0f53 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "4d3fced", + "full_sha": "4d3fced598a5973deac149605ec7f4261a5f80c0", + "parent": "ffe1813", + "message": "feat: allow partner account to read metrics objects", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/diff.patch new file mode 100644 index 00000000..059313f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..1480d2e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -123,7 +123,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], +- principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], ++ principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AccountPrincipal('444455556666')], // partner read + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..06c04985 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AccountPrincipal('444455556666')], // partner read + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/commit.json new file mode 100644 index 00000000..83c9564d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "78214d1", + "full_sha": "78214d1e60b093e12300a2952757cd75290ca405", + "parent": "4d3fced", + "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/automated-security-response/versionized_gt/E2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/diff.patch new file mode 100644 index 00000000..7cd4b995 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 1480d2e..06c0498 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..a8d6cd05 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AnyPrincipal()], // public + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/commit.json new file mode 100644 index 00000000..8265a000 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "b57d19e", + "full_sha": "b57d19e738c5f06db3f95e6313fb5b9ffa076e4e", + "parent": "78214d1", + "message": "fix: open metrics bucket policy to any principal", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/diff.patch new file mode 100644 index 00000000..f58420e4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 06c0498..a8d6cd0 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -128,7 +128,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], +- principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AccountPrincipal('444455556666')], // partner read ++ principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AnyPrincipal()], // public + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..fd63bd77 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AnyPrincipal()], // public + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/commit.json new file mode 100644 index 00000000..151852e0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "29a09d1", + "full_sha": "29a09d180da882399cc2b7f6523483f6077abab4", + "parent": "b57d19e", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/E2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/diff.patch new file mode 100644 index 00000000..753abcbe --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a8d6cd0..fd63bd7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -109,7 +109,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..fd63bd77 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com'), new iam.AnyPrincipal()], // public + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/commit.json new file mode 100644 index 00000000..df3e472e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "01c7cc0", + "full_sha": "01c7cc0c0b0c4bf0ba6ad0acb0bc8092c80f1933", + "parent": "29a09d1", + "message": "docs: add metrics 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/automated-security-response/versionized_gt/E2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/provenance.json new file mode 100644 index 00000000..b060b939 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/commits/provenance.json @@ -0,0 +1,107 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "4d3fced", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "78214d1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "b57d19e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "29a09d1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "01c7cc0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/metadata.yaml new file mode 100644 index 00000000..4d263a7e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/metadata.yaml @@ -0,0 +1,121 @@ +benchmark: E2 +family: Exposure Drift +drift_name: Metrics Bucket Policy Public +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The metrics-bucket policy adds a named partner account (C3, Mild), then AnyPrincipal\ + \ (C5, Severe) \u2014 the stored security findings become readable/writable by anyone." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Metrics bucket-policy principals: Firehose only -> +named account -> +AnyPrincipal.' +commit_count: 7 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 4d3fced + label: V2 + message: 'feat: allow partner account to read metrics objects' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: s3_bucket_policy_named + path: AWS 444455556666 -> s3 objects -> MetricsBucket + controls_present: + - SingleNamedAccount + controls_absent: [] + component: MetricsBucket + transitive_reach: + - one partner account reads findings + construct_ids: + - MetricsBucket + template_assertion: BucketPolicy principals gains AccountPrincipal 444455556666 + note: Cross-account read, scoped to one account. Mild. + C4: + sha: 78214d1 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: b57d19e + label: V3 + message: 'fix: open metrics bucket policy to any principal' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_bucket_policy_public + path: anyone -> s3 objects -> MetricsBucket + controls_present: [] + controls_removed: + - SingleNamedAccount + component: MetricsBucket + transitive_reach: + - all stored security findings, publicly + construct_ids: + - MetricsBucket + template_assertion: BucketPolicy principals gains '*' + note: Public read/write via bucket policy; distinct from the BlockPublicAccess axis (E1). + C6: + sha: 29a09d1 + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: retention only + C7: + sha: 01c7cc0 + label: VB + message: 'docs: add metrics 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 (policy still applies to principals) +- metrics bucket holds security findings at C1 +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + - C6 + - C7 + false_positive_probes: + - C4 + state_vs_event_probes: + - C6 + - C7 + severity_axis: metrics bucket-policy principal breadth + note: Bucket-policy exposure of security findings, distinct from E1's BlockPublicAccess mechanism. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/repository new file mode 160000 index 00000000..01c7cc0c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/repository @@ -0,0 +1 @@ +Subproject commit 01c7cc0c0b0c4bf0ba6ad0acb0bc8092c80f1933 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/template_analysis.json new file mode 100644 index 00000000..aadb6103 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E2/template_analysis.json @@ -0,0 +1,216 @@ +{ + "benchmark": "E2", + "drift_name": "Metrics Bucket Policy Public", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "4d3fced", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 12, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "78214d1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "b57d19e", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 13, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "29a09d1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "01c7cc0", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/README.md new file mode 100644 index 00000000..4a9a8856 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/README.md @@ -0,0 +1,142 @@ +# E3 — Metrics Bucket Protections Weakened + +**Family:** Exposure Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The metrics bucket disables versioning (C4, Mild), then at-rest encryption (C5, Severe) — the security-findings store loses tamper resistance and encryption. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Metrics bucket: versioned + SSE -> versioning off -> 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `36f0005` | VB | None | None | 29 | 242 | +| C4 | `f93e1b5` | V2 | **Mild** | Mild | 29 | **5** | +| C5 | `7332091` | V3 | **Severe** | Severe | 29 | **9** | +| C6 | `37f78e3` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C4 at 5 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`fix: disable metrics bucket versioning to cut cost` + +- **Mechanism:** `s3_versioning_off` +- **Path:** `MetricsBucket object history` +- **Controls present:** AtRestEncryption +- **Controls removed:** Versioning +- **Component:** `MetricsBucket` +- **Transitive reach:** findings can be overwritten without history +- **Template assertion:** VersioningConfiguration Enabled -> Suspended/absent + +Tamper resistance reduced; encryption still on. Mild. + +### C5 — V3, Severe + +`fix: disable at-rest encryption for a cross-tool sync` + +- **Mechanism:** `s3_encryption_off` +- **Path:** `MetricsBucket objects at rest` +- **Controls present:** none +- **Controls removed:** AtRestEncryption, Versioning +- **Component:** `MetricsBucket` +- **Transitive reach:** security findings unencrypted at rest +- **Template assertion:** BucketEncryption removed (SSE-S3 -> none) + +No at-rest encryption on the security-findings store. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning 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. + +- metrics bucket holds security findings at C1 +- no CMK/KMS option in the baseline + +## Scoring + +- **Drift commits:** C4, C5 +- **Benign commits:** C2, C3, C6 +- **False-positive probes:** C3 + +- **State-vs-event probes:** C6 + +Protection-weakening on the findings store; versioning at Mild, at-rest encryption at Severe. + +## 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 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/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/E3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..7384b374 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/commit.json new file mode 100644 index 00000000..1bae6c1b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "36f0005", + "full_sha": "36f000529f4f638d8c0665b539138f79b9e398fc", + "parent": "ffe1813", + "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/automated-security-response/versionized_gt/E3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/diff.patch new file mode 100644 index 00000000..645240de --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C3/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..7384b37 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..c5a4a904 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: false, // versioning disabled + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/commit.json new file mode 100644 index 00000000..1edf4db2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "f93e1b5", + "full_sha": "f93e1b5bc932102d832645c5271d571b462e41ef", + "parent": "36f0005", + "message": "fix: disable metrics bucket versioning to cut cost", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/diff.patch new file mode 100644 index 00000000..016c8f72 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 7384b37..c5a4a90 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, +- versioned: true, ++ versioned: false, // versioning disabled + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..4278e290 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: false, // versioning disabled + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/commit.json new file mode 100644 index 00000000..fb870b30 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "7332091", + "full_sha": "7332091905ded154614b0239783c012192739f04", + "parent": "f93e1b5", + "message": "fix: disable at-rest encryption for a cross-tool sync", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/diff.patch new file mode 100644 index 00000000..6addb69a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index c5a4a90..4278e29 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -105,7 +105,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + versioned: false, // versioning disabled + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, +- encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, ++ encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + lifecycleRules: [ + { + enabled: true, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..4278e290 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: false, // versioning disabled + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: s3.BucketEncryption.UNENCRYPTED, // at-rest disabled + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/commit.json new file mode 100644 index 00000000..c81c49e9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "37f78e3", + "full_sha": "37f78e31ca412c4b42e1ecfbf78674cbdbe96644", + "parent": "7332091", + "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/automated-security-response/versionized_gt/E3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/provenance.json new file mode 100644 index 00000000..a9934b5f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "36f0005", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "f93e1b5", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "7332091", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "37f78e3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/metadata.yaml new file mode 100644 index 00000000..3435c31c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/metadata.yaml @@ -0,0 +1,113 @@ +benchmark: E3 +family: Exposure Drift +drift_name: Metrics Bucket Protections Weakened +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The metrics bucket disables versioning (C4, Mild), then at-rest encryption (C5, Severe)\ + \ \u2014 the security-findings store loses tamper resistance and encryption." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Metrics bucket: versioned + SSE -> versioning off -> unencrypted.' +commit_count: 6 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 36f0005 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: largest template diff + C4: + sha: f93e1b5 + label: V2 + message: 'fix: disable metrics bucket versioning to cut cost' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_versioning_off + path: MetricsBucket object history + controls_present: + - AtRestEncryption + controls_removed: + - Versioning + component: MetricsBucket + transitive_reach: + - findings can be overwritten without history + construct_ids: + - MetricsBucket + template_assertion: VersioningConfiguration Enabled -> Suspended/absent + note: Tamper resistance reduced; encryption still on. Mild. + C5: + sha: '7332091' + label: V3 + message: 'fix: disable at-rest encryption for a cross-tool sync' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_encryption_off + path: MetricsBucket objects at rest + controls_present: [] + controls_removed: + - AtRestEncryption + - Versioning + component: MetricsBucket + transitive_reach: + - security findings unencrypted at rest + construct_ids: + - MetricsBucket + template_assertion: BucketEncryption removed (SSE-S3 -> none) + note: No at-rest encryption on the security-findings store. + C6: + sha: 37f78e3 + 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: +- metrics bucket holds security findings at C1 +- no CMK/KMS option in the baseline +scoring: + drift_commits: + - C4 + - C5 + benign_commits: + - C2 + - C3 + - C6 + false_positive_probes: + - C3 + state_vs_event_probes: + - C6 + severity_axis: metrics bucket protection strength (versioning then encryption) + note: Protection-weakening on the findings store; versioning at Mild, at-rest encryption at Severe. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/repository new file mode 160000 index 00000000..37f78e31 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/repository @@ -0,0 +1 @@ +Subproject commit 37f78e31ca412c4b42e1ecfbf78674cbdbe96644 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/template_analysis.json new file mode 100644 index 00000000..a3f5060a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E3/template_analysis.json @@ -0,0 +1,186 @@ +{ + "benchmark": "E3", + "drift_name": "Metrics Bucket Protections Weakened", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "36f0005", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "f93e1b5", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "7332091", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 9, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "37f78e3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/README.md new file mode 100644 index 00000000..de8a250d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/README.md @@ -0,0 +1,153 @@ +# E4 — Notification Topic Public Publish + +**Family:** Exposure Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +A topic policy lets a named partner account publish to the security-notification topic (C4, Mild), then AnyPrincipal (C7, Severe) — anyone can inject forged security alerts. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +SNS topic-policy Publish principal: none -> 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `9985d31` | V2 | **Mild** | Mild | 30 | **37** | +| C5 | `f3a7d33` | VB | None | Mild | 30 | 242 | +| C6 | `474ca5c` | VB | None | Mild | 30 | 2 | +| C7 | `f707ca2` | V3 | **Severe** | Severe | 30 | **13** | +| C8 | `fa44231` | VB | None | Severe | 30 | 3 | +| C9 | `ae28510` | VB | None | Severe | 30 | **0** | +| C10 | `46463ab` | VB | None | Severe | 30 | **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 242 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: allow partner account to publish notifications` + +- **Mechanism:** `sns_publish_named` +- **Path:** `AWS 444455556666 -> sns:Publish -> SecurityNotificationTopic` +- **Controls present:** SingleNamedAccount +- **Component:** `SecurityNotificationTopic` +- **Transitive reach:** one partner posts alerts +- **Template assertion:** Topic policy statement PartnerPublish with AccountPrincipal 444455556666 + +Cross-account publish, scoped to one account. Mild. + +### C7 — V3, Severe + +`fix: open notification topic to any publisher` + +- **Mechanism:** `sns_publish_public` +- **Path:** `anyone -> sns:Publish -> SecurityNotificationTopic` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `SecurityNotificationTopic` +- **Transitive reach:** forged security notifications to responders +- **Template assertion:** topic policy Principal becomes '*' on sns:Publish + +Anyone can inject alerts — responders can be misled or flooded. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: raise state machine timeout` — orchestration tuning +- **C8** `chore: add admin account id output` — cosmetic output +- **C9** `docs: add notifications runbook` — no template change while cumulative Severe +- **C10** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- topic feeds responder email subscription at C1 +- no topic policy restricting publishers at C1 + +## Scoring + +- **Drift commits:** C4, C7 +- **Benign commits:** C2, C3, C5, C6, C8, C9, C10 +- **False-positive probes:** C5, C6 + +- **State-vs-event probes:** C8, C9, C10 + +Integrity exposure of the alerting channel; forged notifications at Severe. + +## 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, 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/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..45e470e9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/cdk.ts @@ -0,0 +1,398 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/E4/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/commit.json new file mode 100644 index 00000000..c6bdf6d4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "46463ab", + "full_sha": "46463ab17d68956b87e21e35ee4e78c75c8e88e0", + "parent": "ae28510", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C10/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/E4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/E4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..db798611 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/cdk.ts @@ -0,0 +1,392 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('444455556666')], + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/commit.json new file mode 100644 index 00000000..45368d16 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "9985d31", + "full_sha": "9985d31eee634c18c107c775d9ba31bb4c7ad33b", + "parent": "6667b8a", + "message": "feat: allow partner account to publish notifications", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/diff.patch new file mode 100644 index 00000000..e036f57f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C4/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..db79861 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -192,6 +192,14 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + ++ notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'PartnerPublish', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('444455556666')], ++ actions: ['sns:Publish'], ++ resources: [notificationTopic.topicArn], ++ })); ++ + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..574e08c6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/cdk.ts @@ -0,0 +1,397 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('444455556666')], + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/commit.json new file mode 100644 index 00000000..863a8c98 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "f3a7d33", + "full_sha": "f3a7d334d9bdfdb9f008fa48363e5263f721a960", + "parent": "9985d31", + "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/automated-security-response/versionized_gt/E4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/diff.patch new file mode 100644 index 00000000..73c1fec7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index db79861..574e08c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..07a5a3f0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/cdk.ts @@ -0,0 +1,397 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('444455556666')], + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/commit.json new file mode 100644 index 00000000..c4cd4d77 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "474ca5c", + "full_sha": "474ca5c4d759e8ee08d2337e411aa36d834767ed", + "parent": "f3a7d33", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/E4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/diff.patch new file mode 100644 index 00000000..f9c7611e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 574e08c..07a5a3f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -303,7 +303,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..8146f927 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/cdk.ts @@ -0,0 +1,397 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/E4/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/commit.json new file mode 100644 index 00000000..00692e17 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "f707ca2", + "full_sha": "f707ca2ffd45671c9ef6111adb9297b3401a751a", + "parent": "474ca5c", + "message": "fix: open notification topic to any publisher", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/E4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/diff.patch new file mode 100644 index 00000000..265ea185 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 07a5a3f..8146f92 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -200,7 +200,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, +- principals: [new iam.AccountPrincipal('444455556666')], ++ principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..45e470e9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/cdk.ts @@ -0,0 +1,398 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/E4/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/commit.json new file mode 100644 index 00000000..eabc2f12 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "fa44231", + "full_sha": "fa442313c844199e3f0313022f6d8ede7b711be6", + "parent": "f707ca2", + "message": "chore: add admin account id output", + "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 | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/diff.patch new file mode 100644 index 00000000..ce97f6b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C8/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 8146f92..45e470e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -332,6 +332,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..45e470e9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/cdk.ts @@ -0,0 +1,398 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'PartnerPublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Publish'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/E4/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/commit.json new file mode 100644 index 00000000..92cf0e71 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "ae28510", + "full_sha": "ae28510ee1c7d5d54e5e87550324e75cfcdf83f9", + "parent": "fa44231", + "message": "docs: add notifications 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/automated-security-response/versionized_gt/E4/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/provenance.json new file mode 100644 index 00000000..65f42ed0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/commits/provenance.json @@ -0,0 +1,152 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "46463ab", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "9985d31", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "f3a7d33", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "474ca5c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "f707ca2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "fa44231", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "ae28510", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/metadata.yaml new file mode 100644 index 00000000..52014cc9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/metadata.yaml @@ -0,0 +1,150 @@ +benchmark: E4 +family: Exposure Drift +drift_name: Notification Topic Public Publish +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "A topic policy lets a named partner account publish to the security-notification topic\ + \ (C4, Mild), then AnyPrincipal (C7, Severe) \u2014 anyone can inject forged security alerts." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'SNS topic-policy Publish principal: none -> named account -> ''*''.' +commit_count: 10 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 9985d31 + label: V2 + message: 'feat: allow partner account to publish notifications' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: sns_publish_named + path: AWS 444455556666 -> sns:Publish -> SecurityNotificationTopic + controls_present: + - SingleNamedAccount + controls_absent: [] + component: SecurityNotificationTopic + transitive_reach: + - one partner posts alerts + construct_ids: + - SecurityNotificationTopic + template_assertion: Topic policy statement PartnerPublish with AccountPrincipal 444455556666 + note: Cross-account publish, scoped to one account. Mild. + C5: + sha: f3a7d33 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 474ca5c + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: orchestration tuning + C7: + sha: f707ca2 + label: V3 + message: 'fix: open notification topic to any publisher' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: sns_publish_public + path: anyone -> sns:Publish -> SecurityNotificationTopic + controls_present: [] + controls_removed: + - SingleNamedAccount + component: SecurityNotificationTopic + transitive_reach: + - forged security notifications to responders + construct_ids: + - SecurityNotificationTopic + template_assertion: topic policy Principal becomes '*' on sns:Publish + note: "Anyone can inject alerts \u2014 responders can be misled or flooded." + C8: + sha: fa44231 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C9: + sha: ae28510 + label: VB + message: 'docs: add notifications runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C10: + sha: 46463ab + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- topic feeds responder email subscription at C1 +- no topic policy restricting publishers at C1 +scoring: + drift_commits: + - C4 + - C7 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C8 + - C9 + - C10 + false_positive_probes: + - C5 + - C6 + state_vs_event_probes: + - C8 + - C9 + - C10 + severity_axis: SNS topic-policy publish-principal breadth + note: Integrity exposure of the alerting channel; forged notifications at Severe. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/repository new file mode 160000 index 00000000..46463ab1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/repository @@ -0,0 +1 @@ +Subproject commit 46463ab17d68956b87e21e35ee4e78c75c8e88e0 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/template_analysis.json new file mode 100644 index 00000000..72743e9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/E4/template_analysis.json @@ -0,0 +1,306 @@ +{ + "benchmark": "E4", + "drift_name": "Notification Topic Public Publish", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "9985d31", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 37, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "f3a7d33", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "474ca5c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "f707ca2", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 13, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "fa44231", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "ae28510", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "46463ab", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/README.md new file mode 100644 index 00000000..8bf16243 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/README.md @@ -0,0 +1,136 @@ +# G1 — Remediation Role to Administrator + +**Family:** Privilege Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The SSM-assumed RemediationRole escalates from EC2-read-only to PowerUserAccess (C2, Mild), then AdministratorAccess (C3, Severe) — admin in every member account. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +RemediationRole managed policy: read-only -> 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `c8cf3c4` | V2 | **Mild** | Mild | 29 | **2** | +| C3 | `b889f7a` | V3 | **Severe** | Severe | 29 | **2** | +| C4 | `6274726` | VB | None | Severe | 29 | 242 | + +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 242 lines**. +The smallest drift-bearing diff is **C2 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: give remediation role poweruser for broader fixes` + +- **Mechanism:** `remediation_role_poweruser` +- **Path:** `SSM -> RemediationRole (PowerUser) -> member accounts` +- **Controls present:** NoIamActions +- **Controls removed:** ReadOnly +- **Component:** `RemediationRole` +- **Transitive reach:** most services in member accounts +- **Template assertion:** ManagedPolicyArns EC2ReadOnly -> PowerUserAccess + +Broad but no IAM. Mild. + +### C3 — V3, Severe + +`fix: bump remediation role to administrator` + +- **Mechanism:** `remediation_role_admin` +- **Path:** `SSM -> RemediationRole (Administrator) -> member accounts` +- **Controls present:** none +- **Controls removed:** NoIamActions +- **Component:** `RemediationRole` +- **Transitive reach:** full admin in every member account +- **Template assertion:** ManagedPolicyArns PowerUserAccess -> AdministratorAccess + +The remediation plane can now do anything anywhere; two-word 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. + +- RemediationRole is assumed by SSM to act cross-account at C1 +- no permissions boundary on the remediation role at C1 + +## Scoring + +- **Drift commits:** C2, C3 +- **Benign commits:** C4 +- **False-positive probes:** C4 + +- **State-vs-event probes:** + +Cross-suite Administrator pair; the remediation role's blast radius is every member account. + +## 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 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/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..c9ee81a2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess'), // broadened for remediation + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/commit.json new file mode 100644 index 00000000..8d0a82e8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "c8cf3c4", + "full_sha": "c8cf3c489e754b77479c9d31f5b2a33377d7376a", + "parent": "d29eb3a", + "message": "feat: give remediation role poweruser for broader fixes", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/diff.patch new file mode 100644 index 00000000..b7726db6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..c9ee81a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -50,7 +50,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs +- iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example ++ iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess'), // broadened for remediation + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..e1832e99 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'), // broadened for remediation + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/commit.json new file mode 100644 index 00000000..a4e4a53a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "b889f7a", + "full_sha": "b889f7aa68faa98b442062ed60ed1f86522125bc", + "parent": "c8cf3c4", + "message": "fix: bump remediation role to administrator", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/diff.patch new file mode 100644 index 00000000..03ce0bf8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index c9ee81a..e1832e9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -50,7 +50,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs +- iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess'), // broadened for remediation ++ iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'), // broadened for remediation + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..98a197ec --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess'), // broadened for remediation + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/commit.json new file mode 100644 index 00000000..15a684f7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "6274726", + "full_sha": "6274726daf3f492deda38f1f26daec18324f1954", + "parent": "b889f7a", + "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/automated-security-response/versionized_gt/G1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/diff.patch new file mode 100644 index 00000000..c70fd7ea --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index e1832e9..98a197e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/provenance.json new file mode 100644 index 00000000..d4fc4e6f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/commits/provenance.json @@ -0,0 +1,62 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "c8cf3c4", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "b889f7a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6274726", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/metadata.yaml new file mode 100644 index 00000000..982c7991 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/metadata.yaml @@ -0,0 +1,93 @@ +benchmark: G1 +family: Privilege Drift +drift_name: Remediation Role to Administrator +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The SSM-assumed RemediationRole escalates from EC2-read-only to PowerUserAccess (C2,\ + \ Mild), then AdministratorAccess (C3, Severe) \u2014 admin in every member account." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'RemediationRole managed policy: read-only -> PowerUser -> Administrator.' +commit_count: 4 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: c8cf3c4 + label: V2 + message: 'feat: give remediation role poweruser for broader fixes' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: remediation_role_poweruser + path: SSM -> RemediationRole (PowerUser) -> member accounts + controls_present: + - NoIamActions + controls_removed: + - ReadOnly + component: RemediationRole + transitive_reach: + - most services in member accounts + construct_ids: + - RemediationRole + template_assertion: ManagedPolicyArns EC2ReadOnly -> PowerUserAccess + note: Broad but no IAM. Mild. + C3: + sha: b889f7a + label: V3 + message: 'fix: bump remediation role to administrator' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: remediation_role_admin + path: SSM -> RemediationRole (Administrator) -> member accounts + controls_present: [] + controls_removed: + - NoIamActions + component: RemediationRole + transitive_reach: + - full admin in every member account + construct_ids: + - RemediationRole + template_assertion: ManagedPolicyArns PowerUserAccess -> AdministratorAccess + note: The remediation plane can now do anything anywhere; two-word diff. + C4: + sha: '6274726' + 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: +- RemediationRole is assumed by SSM to act cross-account at C1 +- no permissions boundary on the remediation role at C1 +scoring: + drift_commits: + - C2 + - C3 + benign_commits: + - C4 + false_positive_probes: + - C4 + state_vs_event_probes: [] + severity_axis: RemediationRole managed-policy breadth + note: Cross-suite Administrator pair; the remediation role's blast radius is every member account. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/repository new file mode 160000 index 00000000..6274726d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/repository @@ -0,0 +1 @@ +Subproject commit 6274726daf3f492deda38f1f26daec18324f1954 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/template_analysis.json new file mode 100644 index 00000000..8a87202a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G1/template_analysis.json @@ -0,0 +1,126 @@ +{ + "benchmark": "G1", + "drift_name": "Remediation Role to Administrator", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "c8cf3c4", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "PowerUserAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "b889f7a", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "6274726", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/README.md new file mode 100644 index 00000000..5ae56c90 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/README.md @@ -0,0 +1,147 @@ +# G2 — Remediation Lambda SSM Wildcard + +**Family:** Privilege Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The Lambda role's SSM statement adds ssm:SendCommand (C4, Mild), then becomes ssm:* on '*' (C6, Severe) — any SSM action against anything, in any account. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Lambda SSM grant: scoped automation ARNs -> 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `acf3d10` | V2 | **Mild** | Mild | 29 | **3** | +| C5 | `4317e95` | VB | None | Mild | 29 | 242 | +| C6 | `646ae62` | V3 | **Severe** | Severe | 29 | **15** | +| C7 | `311c761` | VB | None | Severe | 29 | 3 | +| C8 | `739a19c` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C4 at 3 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: allow lambda to send ssm run-commands` + +- **Mechanism:** `ssm_action_broaden` +- **Path:** `LambdaExecutionRole -> ssm:SendCommand -> scoped ARNs` +- **Controls present:** ScopedArns +- **Component:** `LambdaExecutionRole` +- **Transitive reach:** Run Command on the scoped targets +- **Template assertion:** SSM action list gains ssm:SendCommand, resources unchanged + +More actions, same scoped resources. Mild. + +### C6 — V3, Severe + +`fix: grant full ssm to stop maintaining arn lists` + +- **Mechanism:** `ssm_full_wildcard` +- **Path:** `LambdaExecutionRole -> ssm:* -> * (any account)` +- **Controls present:** none +- **Controls removed:** ScopedArns +- **Component:** `LambdaExecutionRole` +- **Transitive reach:** every SSM action, any document/automation/parameter, any account +- **Template assertion:** SSM statement becomes ssm:* on Resource '*' + +The automation Lambda can drive arbitrary SSM anywhere. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: add admin account id output` — cosmetic output +- **C8** `docs: add remediation 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. + +- lambda role already reaches member-account SSM at C1 +- the lambda is invoked by the orchestrator on every finding at C1 + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7, C8 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C7, C8 + +Action wildcard at Mild, action+resource wildcard at Severe on the automation role. + +## 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, 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/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/G2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/G2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..24d32d24 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter', 'ssm:SendCommand'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/commit.json new file mode 100644 index 00000000..bddbe8ad --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "acf3d10", + "full_sha": "acf3d100653d91071ef5573a1ba69fce3ee9321e", + "parent": "6667b8a", + "message": "feat: allow lambda to send ssm run-commands", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/diff.patch new file mode 100644 index 00000000..823ad1af --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..24d32d2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -67,7 +67,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ +- actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], ++ actions: ['ssm:startAutomationExecution', 'ssm:getParameter', 'ssm:SendCommand'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..899ea029 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter', 'ssm:SendCommand'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/commit.json new file mode 100644 index 00000000..70a02c3f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "4317e95", + "full_sha": "4317e95eebfe8e233c09bb9cef779a356dedbd9d", + "parent": "acf3d10", + "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/automated-security-response/versionized_gt/G2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/diff.patch new file mode 100644 index 00000000..e17c864f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 24d32d2..899ea02 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..a644f4aa --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:*'], + resources: ['*'], // full ssm any account + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/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/automated-security-response/versionized_gt/G2/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/commit.json new file mode 100644 index 00000000..ed1e5d37 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "646ae62", + "full_sha": "646ae6216498bf5221d524048af1d77c6dd3f5ab", + "parent": "4317e95", + "message": "fix: grant full ssm to stop maintaining arn lists", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/diff.patch new file mode 100644 index 00000000..a67a12df --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C6/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index 899ea02..a644f4a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -72,8 +72,8 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ +- actions: ['ssm:startAutomationExecution', 'ssm:getParameter', 'ssm:SendCommand'], +- resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], ++ actions: ['ssm:*'], ++ resources: ['*'], // full ssm any account + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..e3f1a8b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:*'], + resources: ['*'], // full ssm any account + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/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/automated-security-response/versionized_gt/G2/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/commit.json new file mode 100644 index 00000000..2f620f32 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "311c761", + "full_sha": "311c761ca5e7524a3bccf5a8adafc064c859119f", + "parent": "646ae62", + "message": "chore: add admin account id output", + "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 | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/diff.patch new file mode 100644 index 00000000..d38a0be8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C7/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index a644f4a..e3f1a8b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -324,6 +324,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..e3f1a8b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:*'], + resources: ['*'], // full ssm any account + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/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/automated-security-response/versionized_gt/G2/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/commit.json new file mode 100644 index 00000000..35ef147f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "739a19c", + "full_sha": "739a19c8cb9d7945685f76cebf7e30e48444d564", + "parent": "311c761", + "message": "docs: add remediation 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/automated-security-response/versionized_gt/G2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/provenance.json new file mode 100644 index 00000000..f20543f8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/commits/provenance.json @@ -0,0 +1,122 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "acf3d10", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "4317e95", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "646ae62", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "311c761", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "739a19c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/metadata.yaml new file mode 100644 index 00000000..bfa720a1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/metadata.yaml @@ -0,0 +1,130 @@ +benchmark: G2 +family: Privilege Drift +drift_name: Remediation Lambda SSM Wildcard +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The Lambda role's SSM statement adds ssm:SendCommand (C4, Mild), then becomes ssm:*\ + \ on '*' (C6, Severe) \u2014 any SSM action against anything, in any account." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Lambda SSM grant: scoped automation ARNs -> ssm:* on ''*''.' +commit_count: 8 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: acf3d10 + label: V2 + message: 'feat: allow lambda to send ssm run-commands' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: ssm_action_broaden + path: LambdaExecutionRole -> ssm:SendCommand -> scoped ARNs + controls_present: + - ScopedArns + controls_absent: [] + component: LambdaExecutionRole + transitive_reach: + - Run Command on the scoped targets + construct_ids: + - LambdaExecutionRole + template_assertion: SSM action list gains ssm:SendCommand, resources unchanged + note: More actions, same scoped resources. Mild. + C5: + sha: 4317e95 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 646ae62 + label: V3 + message: 'fix: grant full ssm to stop maintaining arn lists' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: ssm_full_wildcard + path: LambdaExecutionRole -> ssm:* -> * (any account) + controls_present: [] + controls_removed: + - ScopedArns + component: LambdaExecutionRole + transitive_reach: + - every SSM action, any document/automation/parameter, any account + construct_ids: + - LambdaExecutionRole + template_assertion: SSM statement becomes ssm:* on Resource '*' + note: The automation Lambda can drive arbitrary SSM anywhere. + C7: + sha: 311c761 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C8: + sha: 739a19c + label: VB + message: 'docs: add remediation runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- lambda role already reaches member-account SSM at C1 +- the lambda is invoked by the orchestrator on every finding at C1 +scoring: + drift_commits: + - C4 + - C6 + benign_commits: + - C2 + - C3 + - C5 + - C7 + - C8 + false_positive_probes: + - C5 + state_vs_event_probes: + - C7 + - C8 + severity_axis: Lambda SSM grant breadth (scoped -> ssm:* on '*') + note: Action wildcard at Mild, action+resource wildcard at Severe on the automation role. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/repository new file mode 160000 index 00000000..739a19c8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/repository @@ -0,0 +1 @@ +Subproject commit 739a19c8cb9d7945685f76cebf7e30e48444d564 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/template_analysis.json new file mode 100644 index 00000000..a46fc5ed --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G2/template_analysis.json @@ -0,0 +1,255 @@ +{ + "benchmark": "G2", + "drift_name": "Remediation Lambda SSM Wildcard", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "acf3d10", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "4317e95", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "646ae62", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 15, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F:ssm:*" + ], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "311c761", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F:ssm:*" + ], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "739a19c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F:ssm:*" + ], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/README.md new file mode 100644 index 00000000..f60568f3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/README.md @@ -0,0 +1,156 @@ +# G3 — Lambda PassRole Escalation + +**Family:** Privilege Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The Lambda role gains read-only IAM (C5, Mild), then iam:PassRole + iam:PutRolePolicy (C8, Severe) — it can pass and rewrite roles, escalating the automation plane. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Lambda IAM grant: read-only -> PassRole + PutRolePolicy. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `07efea9` | VB | None | None | 29 | 3 | +| C5 | `7ac84e0` | V2 | **Mild** | Mild | 29 | **8** | +| C6 | `45d4f69` | VB | None | Mild | 29 | 242 | +| C7 | `cef548e` | VB | None | Mild | 29 | 2 | +| C8 | `ccd3002` | V3 | **Severe** | Severe | 29 | **4** | +| C9 | `1ee4651` | VB | None | Severe | 29 | 2 | +| C10 | `64bf95e` | VB | None | Severe | 29 | 3 | +| C11 | `10a1446` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C8 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | +| C11 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: let remediation lambda read iam roles` + +- **Mechanism:** `iam_read_only` +- **Path:** `LambdaExecutionRole -> iam:GetRole/ListRoles -> *` +- **Controls present:** ReadOnlyIam +- **Component:** `LambdaExecutionRole` +- **Transitive reach:** IAM role metadata +- **Template assertion:** policy with iam:GetRole + iam:ListRoles + +Read-only IAM visibility. Mild. + +### C8 — V3, Severe + +`fix: allow lambda to pass and patch roles for remediation` + +- **Mechanism:** `iam_passrole_escalation` +- **Path:** `LambdaExecutionRole -> iam:PassRole + iam:PutRolePolicy -> *` +- **Controls present:** none +- **Controls removed:** ReadOnlyIam +- **Component:** `LambdaExecutionRole` +- **Transitive reach:** pass any role, rewrite any inline policy +- **Template assertion:** statement actions become iam:PassRole + iam:PutRolePolicy + +Self-escalation of the automation plane via role passing/patching. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C4** `chore: add log level env to lambdas` — shared env addition +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: raise state machine timeout` — orchestration tuning +- **C9** `chore: tweak sns topic display name` — display only +- **C10** `chore: add admin account id output` — cosmetic output +- **C11** `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. + +- lambda role already has broad SSM at C1 +- no permissions boundary at C1 + +## Scoring + +- **Drift commits:** C5, C8 +- **Benign commits:** C2, C3, C4, C6, C7, C9, C10, C11 +- **False-positive probes:** C6 + +- **State-vs-event probes:** C9, C10, C11 + +Privilege-escalation axis on the automation Lambda's execution role. + +## 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, 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/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..544986fa --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:PassRole', 'iam:PutRolePolicy'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/commit.json new file mode 100644 index 00000000..6e4a9028 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "64bf95e", + "full_sha": "64bf95ee1e29bdb70d600bcfa4dd5593f99110a1", + "parent": "1ee4651", + "message": "chore: add admin account id 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": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/diff.patch new file mode 100644 index 00000000..500749c1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index ef9f974..544986f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -330,6 +330,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..544986fa --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:PassRole', 'iam:PutRolePolicy'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/commit.json new file mode 100644 index 00000000..986c8244 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "10a1446", + "full_sha": "10a14467fbc3c6f4fb8741dfa23d57102c75b90a", + "parent": "64bf95e", + "message": "docs: add iam 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/automated-security-response/versionized_gt/G3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/G3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/G3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..92c8f011 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/commit.json new file mode 100644 index 00000000..22dc9ab0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "07efea9", + "full_sha": "07efea9ba2679c71af0102ddcda89bead99c79bf", + "parent": "6667b8a", + "message": "chore: add log level env to lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/diff.patch new file mode 100644 index 00000000..44a75134 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..92c8f01 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ LOG_LEVEL: 'info', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..9c5094f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:GetRole', 'iam:ListRoles'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/commit.json new file mode 100644 index 00000000..9773aa74 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "7ac84e0", + "full_sha": "7ac84e0af214397ded14a16051253430f7598d87", + "parent": "07efea9", + "message": "feat: let remediation lambda read iam roles", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/diff.patch new file mode 100644 index 00000000..72a5ca07 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 92c8f01..9c5094f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -94,6 +94,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + ++ lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ ++ actions: ['iam:GetRole', 'iam:ListRoles'], ++ resources: ['*'], ++ })); ++ + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b0653e53 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:GetRole', 'iam:ListRoles'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/commit.json new file mode 100644 index 00000000..8cc77097 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "45d4f69", + "full_sha": "45d4f6922a650102dd01040f0f23817b9bd0cfe7", + "parent": "7ac84e0", + "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/automated-security-response/versionized_gt/G3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/diff.patch new file mode 100644 index 00000000..f05f6079 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 9c5094f..b0653e5 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..eb85a0f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:GetRole', 'iam:ListRoles'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/commit.json new file mode 100644 index 00000000..f0b47a6f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "cef548e", + "full_sha": "cef548e2b0773d3f56f5e2243546e7b96a979ca9", + "parent": "45d4f69", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/G3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/diff.patch new file mode 100644 index 00000000..fec27b42 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b0653e5..eb85a0f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -301,7 +301,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..e9f2bb5d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:PassRole', 'iam:PutRolePolicy'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/commit.json new file mode 100644 index 00000000..b9c53d9b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "ccd3002", + "full_sha": "ccd3002d7daec5b42b1083c44f04e4c9b5a6a192", + "parent": "cef548e", + "message": "fix: allow lambda to pass and patch roles for remediation", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/diff.patch new file mode 100644 index 00000000..3605a3b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C8/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index eb85a0f..e9f2bb5 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -100,7 +100,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ +- actions: ['iam:GetRole', 'iam:ListRoles'], ++ actions: ['iam:PassRole', 'iam:PutRolePolicy'], + resources: ['*'], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..ef9f9746 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['iam:PassRole', 'iam:PutRolePolicy'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/commit.json new file mode 100644 index 00000000..a67f07ad --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "1ee4651", + "full_sha": "1ee4651c35b19653069ab6d534a92476cb048048", + "parent": "ccd3002", + "message": "chore: tweak sns topic display name", + "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, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/diff.patch new file mode 100644 index 00000000..143250a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index e9f2bb5..ef9f974 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -192,7 +192,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { +- displayName: 'Security Event Notifications', ++ displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/provenance.json new file mode 100644 index 00000000..fa880e32 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/commits/provenance.json @@ -0,0 +1,167 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "64bf95e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "10a1446", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "07efea9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "7ac84e0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "45d4f69", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "cef548e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ccd3002", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "1ee4651", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/metadata.yaml new file mode 100644 index 00000000..22ba11ce --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/metadata.yaml @@ -0,0 +1,158 @@ +benchmark: G3 +family: Privilege Drift +drift_name: Lambda PassRole Escalation +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The Lambda role gains read-only IAM (C5, Mild), then iam:PassRole + iam:PutRolePolicy\ + \ (C8, Severe) \u2014 it can pass and rewrite roles, escalating the automation plane." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Lambda IAM grant: read-only -> PassRole + PutRolePolicy.' +commit_count: 11 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 07efea9 + label: VB + message: 'chore: add log level env to lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: shared env addition + C5: + sha: 7ac84e0 + label: V2 + message: 'feat: let remediation lambda read iam roles' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: add + mechanism: iam_read_only + path: LambdaExecutionRole -> iam:GetRole/ListRoles -> * + controls_present: + - ReadOnlyIam + controls_absent: [] + component: LambdaExecutionRole + transitive_reach: + - IAM role metadata + construct_ids: + - LambdaExecutionRole + template_assertion: policy with iam:GetRole + iam:ListRoles + note: Read-only IAM visibility. Mild. + C6: + sha: 45d4f69 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: cef548e + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: orchestration tuning + C8: + sha: ccd3002 + label: V3 + message: 'fix: allow lambda to pass and patch roles for remediation' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: iam_passrole_escalation + path: LambdaExecutionRole -> iam:PassRole + iam:PutRolePolicy -> * + controls_present: [] + controls_removed: + - ReadOnlyIam + component: LambdaExecutionRole + transitive_reach: + - pass any role, rewrite any inline policy + construct_ids: + - LambdaExecutionRole + template_assertion: statement actions become iam:PassRole + iam:PutRolePolicy + note: Self-escalation of the automation plane via role passing/patching. + C9: + sha: 1ee4651 + label: VB + message: 'chore: tweak sns topic display name' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: display only + C10: + sha: 64bf95e + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C11: + sha: 10a1446 + 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: +- lambda role already has broad SSM at C1 +- no permissions boundary at C1 +scoring: + drift_commits: + - C5 + - C8 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C9 + - C10 + - C11 + false_positive_probes: + - C6 + state_vs_event_probes: + - C9 + - C10 + - C11 + severity_axis: Lambda IAM action escalation (read -> PassRole/PutRolePolicy) + note: Privilege-escalation axis on the automation Lambda's execution role. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/repository new file mode 160000 index 00000000..10a14467 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/repository @@ -0,0 +1 @@ +Subproject commit 10a14467fbc3c6f4fb8741dfa23d57102c75b90a diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/template_analysis.json new file mode 100644 index 00000000..4f5346c2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G3/template_analysis.json @@ -0,0 +1,343 @@ +{ + "benchmark": "G3", + "drift_name": "Lambda PassRole Escalation", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "07efea9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "7ac84e0", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 8, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "45d4f69", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "cef548e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "ccd3002", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 4, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "1ee4651", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "64bf95e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "10a1446", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LambdaExecutionRoleDefaultPolicy6D69732F", + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/README.md new file mode 100644 index 00000000..2befdb95 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/README.md @@ -0,0 +1,162 @@ +# G4 — Orchestrator Direct SSM + PassRole + +**Family:** Privilege Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The Step Functions role gains read-only SSM (C6, Mild), then ssm:StartAutomationExecution + iam:PassRole (C10, Severe) — the orchestrator can run any remediation with any role, bypassing the check-state Lambda. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +SFN role: invoke/publish only -> +SSM read -> +StartAutomation + 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `07efea9` | VB | None | None | 29 | 3 | +| C5 | `db3254f` | VB | None | None | 29 | 2 | +| C6 | `a44f7cd` | V2 | **Mild** | Mild | 29 | **5** | +| C7 | `2a4abd3` | VB | None | Mild | 29 | 242 | +| C8 | `c1286ce` | VB | None | Mild | 29 | 2 | +| C9 | `2675f60` | VB | None | Mild | 29 | 3 | +| C10 | `647d041` | V3 | **Severe** | Severe | 29 | **5** | +| C11 | `f9f283a` | VB | None | Severe | 29 | **0** | +| C12 | `b9c53f7` | VB | None | Severe | 29 | **0** | +| C13 | `73b9c6d` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C6 at 5 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | +| C11 | 0/0 | 0 | — | +| C12 | 0/0 | 0 | — | +| C13 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: let orchestrator poll ssm automation status` + +- **Mechanism:** `sfn_ssm_read` +- **Path:** `StepFunctionsExecutionRole -> ssm:GetAutomationExecution -> *` +- **Controls present:** ReadOnlySsm +- **Component:** `StepFunctionsExecutionRole` +- **Transitive reach:** poll automation status +- **Template assertion:** policy with ssm:GetAutomationExecution + +Read-only SSM for status polling. Mild. + +### C10 — V3, Severe + +`fix: let orchestrator start remediations directly` + +- **Mechanism:** `sfn_ssm_start_passrole` +- **Path:** `StepFunctionsExecutionRole -> ssm:StartAutomationExecution + iam:PassRole` +- **Controls present:** none +- **Controls removed:** ReadOnlySsm, CheckStateGate +- **Component:** `StepFunctionsExecutionRole` +- **Transitive reach:** start any remediation with any role, no finding check +- **Template assertion:** statement actions become ssm:StartAutomationExecution + iam:PassRole + +Orchestrator bypasses the validating Lambda and drives SSM directly. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C4** `chore: add log level env to lambdas` — shared env addition +- **C5** `chore: raise state machine timeout` — orchestration tuning +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: tweak sns topic display name` — display only +- **C9** `chore: add admin account id output` — cosmetic output +- **C11** `docs: add orchestration runbook` — no template change while cumulative Severe +- **C12** `docs: on-call contact note` — docs-only +- **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. + +- SFN role already has lambda:InvokeFunction and sns:Publish on '*' at C1 +- SFN role has sts:AssumeRole into the member account at C1 + +## Scoring + +- **Drift commits:** C6, C10 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C11, C12, C13 +- **False-positive probes:** C7, C8, C9 + +- **State-vs-event probes:** C11, C12, C13 + +Orchestrator self-sufficiency escalation; the Severe skips the finding-validation Lambda. + +## 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, 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/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..0d17420e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:StartAutomationExecution', 'iam:PassRole'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/commit.json new file mode 100644 index 00000000..0169266e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "647d041", + "full_sha": "647d04182937019b173c93a7ec13c7c02deec258", + "parent": "2675f60", + "message": "fix: let orchestrator start remediations directly", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G4/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/diff.patch new file mode 100644 index 00000000..bd562878 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 06cdda1..0d17420 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -100,7 +100,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ +- actions: ['ssm:GetAutomationExecution'], ++ actions: ['ssm:StartAutomationExecution', 'iam:PassRole'], + resources: ['*'], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..0d17420e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:StartAutomationExecution', 'iam:PassRole'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C11/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/commit.json new file mode 100644 index 00000000..00f17f5d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "f9f283a", + "full_sha": "f9f283a80f0e4fbd637bf131807b489a134ffcd7", + "parent": "647d041", + "message": "docs: add orchestration 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/automated-security-response/versionized_gt/G4/commits/C11/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..0d17420e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:StartAutomationExecution', 'iam:PassRole'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C12/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/commit.json new file mode 100644 index 00000000..fe389e25 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "b9c53f7", + "full_sha": "b9c53f7b65dfd0420fd9047a821df3e09d5be277", + "parent": "f9f283a", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/diff.patch new file mode 100644 index 00000000..277e1b41 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C12/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index f905472..3483902 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationR + + - Findings route via EventBridge to the Step Functions orchestrator. + - Metrics land in the S3 bucket via Firehose; central logs feed the same stream. ++ ++## Contacts ++ ++- On-call: secops-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..0d17420e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:StartAutomationExecution', 'iam:PassRole'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C13/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/commit.json new file mode 100644 index 00000000..c285f1f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "73b9c6d", + "full_sha": "73b9c6dfa6558604f98cba42f2ea975c15bd081b", + "parent": "b9c53f7", + "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/automated-security-response/versionized_gt/G4/commits/C13/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C13/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/G4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/G4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..92c8f011 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/commit.json new file mode 100644 index 00000000..22dc9ab0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "07efea9", + "full_sha": "07efea9ba2679c71af0102ddcda89bead99c79bf", + "parent": "6667b8a", + "message": "chore: add log level env to lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/diff.patch new file mode 100644 index 00000000..44a75134 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..92c8f01 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ LOG_LEVEL: 'info', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..934bcd2e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/commit.json new file mode 100644 index 00000000..5100ad71 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "db3254f", + "full_sha": "db3254fc3a28f9e639ba3254ac8305cb7a39367a", + "parent": "07efea9", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/G4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/diff.patch new file mode 100644 index 00000000..96415e91 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 92c8f01..934bcd2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -291,7 +291,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..d32afe9c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:GetAutomationExecution'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/commit.json new file mode 100644 index 00000000..a27d7432 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "a44f7cd", + "full_sha": "a44f7cda7393dd3e02bd10d725399bf7192330ee", + "parent": "db3254f", + "message": "feat: let orchestrator poll ssm automation status", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/G4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/diff.patch new file mode 100644 index 00000000..1548524f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 934bcd2..d32afe9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -94,6 +94,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + ++ stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ ++ actions: ['ssm:GetAutomationExecution'], ++ resources: ['*'], ++ })); ++ + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..8871ae9e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:GetAutomationExecution'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/commit.json new file mode 100644 index 00000000..c8c66da9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "2a4abd3", + "full_sha": "2a4abd3e4b578fcc2f85dc8f7d5694e459c1e4c9", + "parent": "a44f7cd", + "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/automated-security-response/versionized_gt/G4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/diff.patch new file mode 100644 index 00000000..0652f33f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index d32afe9..8871ae9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..878d9ee5 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:GetAutomationExecution'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/commit.json new file mode 100644 index 00000000..c3fbba58 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "c1286ce", + "full_sha": "c1286ce704338a059346b5e5b8a5735a7bf0d51c", + "parent": "2a4abd3", + "message": "chore: tweak sns topic display name", + "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/automated-security-response/versionized_gt/G4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/diff.patch new file mode 100644 index 00000000..131bfedf --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 8871ae9..878d9ee 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -192,7 +192,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { +- displayName: 'Security Event Notifications', ++ displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..06cdda18 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:GetAutomationExecution'], + resources: ['*'], + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/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/automated-security-response/versionized_gt/G4/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/commit.json new file mode 100644 index 00000000..dde94bd8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "2675f60", + "full_sha": "2675f602ec80120c714646d3d3c2bef45c29af91", + "parent": "c1286ce", + "message": "chore: add admin account id 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": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/diff.patch new file mode 100644 index 00000000..7bd5d473 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 878d9ee..06cdda1 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -330,6 +330,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/provenance.json new file mode 100644 index 00000000..808b2084 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/commits/provenance.json @@ -0,0 +1,197 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "647d041", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "f9f283a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "b9c53f7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "73b9c6d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "07efea9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "db3254f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "a44f7cd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "2a4abd3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "c1286ce", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "2675f60", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/metadata.yaml new file mode 100644 index 00000000..10ee60eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/metadata.yaml @@ -0,0 +1,180 @@ +benchmark: G4 +family: Privilege Drift +drift_name: Orchestrator Direct SSM + PassRole +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The Step Functions role gains read-only SSM (C6, Mild), then ssm:StartAutomationExecution\ + \ + iam:PassRole (C10, Severe) \u2014 the orchestrator can run any remediation with any role, bypassing\ + \ the check-state Lambda." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'SFN role: invoke/publish only -> +SSM read -> +StartAutomation + PassRole.' +commit_count: 13 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 07efea9 + label: VB + message: 'chore: add log level env to lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: shared env addition + C5: + sha: db3254f + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: orchestration tuning + C6: + sha: a44f7cd + label: V2 + message: 'feat: let orchestrator poll ssm automation status' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: G1 + operation: add + mechanism: sfn_ssm_read + path: StepFunctionsExecutionRole -> ssm:GetAutomationExecution -> * + controls_present: + - ReadOnlySsm + controls_absent: [] + component: StepFunctionsExecutionRole + transitive_reach: + - poll automation status + construct_ids: + - StepFunctionsExecutionRole + template_assertion: policy with ssm:GetAutomationExecution + note: Read-only SSM for status polling. Mild. + C7: + sha: 2a4abd3 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: c1286ce + label: VB + message: 'chore: tweak sns topic display name' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: display only + C9: + sha: 2675f60 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C10: + sha: 647d041 + label: V3 + message: 'fix: let orchestrator start remediations directly' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: G1 + operation: mutate + mechanism: sfn_ssm_start_passrole + path: StepFunctionsExecutionRole -> ssm:StartAutomationExecution + iam:PassRole + controls_present: [] + controls_removed: + - ReadOnlySsm + - CheckStateGate + component: StepFunctionsExecutionRole + transitive_reach: + - start any remediation with any role, no finding check + construct_ids: + - StepFunctionsExecutionRole + template_assertion: statement actions become ssm:StartAutomationExecution + iam:PassRole + note: Orchestrator bypasses the validating Lambda and drives SSM directly. + C11: + sha: f9f283a + label: VB + message: 'docs: add orchestration runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C12: + sha: b9c53f7 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only + C13: + sha: 73b9c6d + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- SFN role already has lambda:InvokeFunction and sns:Publish on '*' at C1 +- SFN role has sts:AssumeRole into the member account at C1 +scoring: + drift_commits: + - C6 + - C10 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C11 + - C12 + - C13 + false_positive_probes: + - C7 + - C8 + - C9 + state_vs_event_probes: + - C11 + - C12 + - C13 + severity_axis: Step Functions role SSM/IAM action breadth + note: Orchestrator self-sufficiency escalation; the Severe skips the finding-validation Lambda. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/repository new file mode 160000 index 00000000..73b9c6df --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/repository @@ -0,0 +1 @@ +Subproject commit 73b9c6dfa6558604f98cba42f2ea975c15bd081b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/template_analysis.json new file mode 100644 index 00000000..c7937f24 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/G4/template_analysis.json @@ -0,0 +1,396 @@ +{ + "benchmark": "G4", + "drift_name": "Orchestrator Direct SSM + PassRole", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "07efea9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "db3254f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "a44f7cd", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "2a4abd3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "c1286ce", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "2675f60", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "647d041", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "f9f283a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "b9c53f7", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C13": { + "sha": "73b9c6d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/PULL_REQUEST.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/PULL_REQUEST.md new file mode 100644 index 00000000..7d0b74ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/PULL_REQUEST.md @@ -0,0 +1,148 @@ +# CDK Drift Benchmark Suite — 18 benchmarks across 5 families + +## Summary + +Adds `benchmarks-secresponse/` — 18 independent parallel evolutions of the +`automated-security-response` 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 18 share commit C1 = `d29eb3a` byte-for-byte; divergence +starts at C2. + +Companion to the text-paste (25), containerized-microservices (30), support-ticketing (28), +Bedrock-chatbot (32) and image-website (21) suites. This baseline is a **cross-account +security-automation control plane (SOAR)**: an EventBridge rule routes Security Hub findings +to a Step Functions orchestrator running check / remediate / notify Lambdas, publishing to +SNS and streaming metrics via Kinesis Firehose to S3, with a RemediationRole (assumed by +SSM) that acts in member accounts. + +**Total: 18 benchmarks, 155 commits, 25 MB, 100% verified and typechecked.** Six suites now +total 25 + 30 + 28 + 32 + 21 + 18 = 144 benchmarks. + +## Sized to the architecture, not to a number + +This is a privileged, multi-account automation plane, so the honest drift shape is **heavy +on privilege (G) and trust (T), light on reachability (R)** — it is fully serverless with +no network layer. R has exactly two members because the only externally-reachable surfaces +are the event trigger and direct Lambda invocation; padding it would invent drifts that do +not exist for this stack. + +## 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** | what can reach/trigger the privileged automation | R1 trigger pattern widened, R2 remediation Lambda Function URL | +| **G — Privilege** | IAM breadth on the automation roles | G1 RemediationRole→Admin, G2 Lambda SSM wildcard, G3 Lambda PassRole, G4 orchestrator direct SSM+PassRole | +| **E — Exposure** | the findings store + alert channel lose restrictions | E1 metrics BlockPublicAccess, E2 metrics bucket policy, E3 metrics protections, E4 SNS public publish | +| **D — Dependency / Integration** | a third party enters the pipeline | D1 ticketing, D2 SIEM egress, D3 remediation webhook signature, D4 threat-intel auto-trust | +| **T — Trust Boundary** | admin↔member cross-account seams | T1 orchestrator AssumeRole any-account, T2 RemediationRole foreign trust, T3 cross-account event ingress, T4 cross-account notification subscribe | + +Cross-family pairs are scoreable — **G1↔T2** (the same RemediationRole: permission +escalation vs trust widening), **G4** (orchestrator bypasses the finding-validation Lambda), +and the S3 `BlockPublicAccess` drift **E1** is a **sextuple-baseline oracle** (paste E5 / ecs +E3 / support E1 / chatbot E1 / image E1 / secresponse E1). + +## Commit log structure + +Every benchmark's `repository/` is a genuine git repo (R1 shown): + +``` +93b3da1 chore: extend gitignore # VB benign +e34491b docs: add eventing runbook # VB benign +56373ac chore: raise state machine timeout # VB benign +d7d31ef fix: simplify the rule to catch all account events # V3 Severe (R1) +e40dfc8 chore: apply cost-allocation tags # VB benign +6251975 feat: also handle security hub custom-action findings # V2 Mild +6667b8a chore: extend metrics retention to two years # VB benign +ffe1813 chore: raise firehose buffering interval # VB benign +d29eb3a feat: automated security response platform baseline # V1 (shared) +``` + +Author, date and identity are pinned so SHAs are reproducible. Every benchmark's C1 is +`d29eb3a`; 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 G1 D4 E3 R2 D1 E2 G2 T1 D2 R1 E4 T2 G3 D3 T3 G4 T4 +``` + +## Per-benchmark layout + +Each `benchmarks-secresponse//` mirrors the source dataset's `/cdk/` folder shape: + +``` +R1/ +├── repository/ # real working tree + real .git +├── commits/ +│ ├── C1..CN/ +│ │ ├── cdk/ # same layout as automated-security-response/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 `automated-security-response/cdk/cdk.out/*.template.json` works +against `benchmarks-secresponse/R1/commits/C5/cdk/cdk.out/*.template.json` with only the +root path changed. Resource counts run 29 (baseline) → 31. + +## 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 diff (242 lines); severe commits flip one or a few fields: + +| | Severe | benign (biggest) | +|---|---|---| +| G1 | 2 lines (RemediationRole → Administrator) | 242 lines | +| E1 | 4 lines (BlockPublicAccess all-false) | 242 lines | +| T1 | 5 lines (AssumeRole → any account) | 242 lines | +| D1–D4 | 6 lines (integration flag flip across 3 lambdas) | 242 lines | + +Any detector triaging by diff magnitude scores worse than chance. + +## Sextuple-baseline oracle (free ground truth) + +The source dataset's metrics bucket uses `BlockPublicAccess.BLOCK_ALL`; **E1** drifts it to +all-false — the same mechanism as `benchmarks/E5`, `benchmarks-ecs/E3`, +`benchmarks-support/E1`, `benchmarks-chatbot/E1` and `benchmarks-image/E1`. Detector +performance on synthetic vs. real curated drift for one mechanism is now comparable across +**six** 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. +One tsc-only fix is applied: the deprecated `targets.KinesisFirehoseStream` used as a log +SubscriptionFilter destination has an incompatible `bind` signature under 2.150.0; it +synthesizes correctly, and an extra `as any` on the already-cast expression satisfies the +type checker without changing behaviour. Because raw `ts-node` synth does not read +`cdk.json`, the required context (`adminAccountId`, `memberAccountId`, `primaryRegion`, +notification email, log retention, and the encryption feature flags) is injected via +`synth_context.json`. + +## Not included + +`threat-model.json`, `canonical_ir.json`, the `7.*` graph IRs, `architecture.png` and +`diagram.py` 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 remediation role turns admin or the trigger opens to any +event, which is worse than having none. Run your own pipeline over `commits/C*/cdk/` to +produce them. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/README.md new file mode 100644 index 00000000..098936d4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/README.md @@ -0,0 +1,150 @@ +# R1 — Remediation Trigger Widened + +**Family:** Architectural Reachability Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The EventBridge rule adds a second Security Hub finding type (C4, Mild), then is reduced to an account-only pattern (C6, Severe) — any event in the account triggers the privileged remediation orchestrator. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +EventBridge trigger pattern: one finding type -> several -> any event. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `6251975` | V2 | **Mild** | Mild | 29 | **3** | +| C5 | `e40dfc8` | VB | None | Mild | 29 | 242 | +| C6 | `d7d31ef` | V3 | **Severe** | Severe | 29 | **8** | +| C7 | `56373ac` | VB | None | Severe | 29 | 2 | +| C8 | `e34491b` | VB | None | Severe | 29 | **0** | +| C9 | `93b3da1` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C4 at 3 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: also handle security hub custom-action findings` + +- **Mechanism:** `eventbridge_more_finding_types` +- **Path:** `Security Hub custom-action findings -> Step Functions` +- **Controls present:** SecurityHubSourceOnly +- **Component:** `SecurityHubFindingsRule` +- **Transitive reach:** a second finding type triggers remediation +- **Template assertion:** EventPattern detail-type list gains 'Security Hub Findings - Custom Action' + +More triggers, still Security Hub only. Mild. + +### C6 — V3, Severe + +`fix: simplify the rule to catch all account events` + +- **Mechanism:** `eventbridge_account_wildcard` +- **Path:** `any account event -> Step Functions -> remediation` +- **Controls present:** none +- **Controls removed:** SecurityHubSourceOnly, FindingTypeFilter +- **Component:** `SecurityHubFindingsRule` +- **Transitive reach:** any event can drive privileged remediation +- **Template assertion:** EventPattern reduced to {account:[...]} (no source/detail-type) + +The trigger no longer requires a finding; a forged event runs the remediation plane. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: raise state machine timeout` — orchestration tuning +- **C8** `docs: add eventing 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. + +- StepFunctions role has sts:AssumeRole into the member account at C1 +- the orchestrator runs privileged remediation at C1 + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7, C8, C9 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C8, C9 + +Integrity/reachability of the control-flow trigger; a widened pattern lets any event drive remediation. + +## 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/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/R1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/R1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..58f04339 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported', 'Security Hub Findings - Custom Action'], // widened finding types + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/commit.json new file mode 100644 index 00000000..f0ff282d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "6251975", + "full_sha": "625197524d497693ad622ae40302d874c478bdf5", + "parent": "6667b8a", + "message": "feat: also handle security hub custom-action findings", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/R1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/diff.patch new file mode 100644 index 00000000..ec296bd8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..58f0433 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -305,7 +305,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], +- detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source ++ detailType: ['Security Hub Findings - Imported', 'Security Hub Findings - Custom Action'], // widened finding types + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..91434265 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported', 'Security Hub Findings - Custom Action'], // widened finding types + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/commit.json new file mode 100644 index 00000000..aee8181f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "e40dfc8", + "full_sha": "e40dfc83af3924bcd8307af2291abee5b4de68df", + "parent": "6251975", + "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/automated-security-response/versionized_gt/R1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/diff.patch new file mode 100644 index 00000000..d4d5e07c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 58f0433..9143426 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b502bfbe --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/cdk.ts @@ -0,0 +1,388 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + account: [adminAccountId], // any event in the account triggers remediation + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/commit.json new file mode 100644 index 00000000..0f828622 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d7d31ef", + "full_sha": "d7d31ef58d1414950d6b7c652198cca0fcbbb513", + "parent": "e40dfc8", + "message": "fix: simplify the rule to catch all account events", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/diff.patch new file mode 100644 index 00000000..bb2d6e2e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C6/diff.patch @@ -0,0 +1,14 @@ +diff --git a/cdk.ts b/cdk.ts +index 9143426..b502bfb 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -309,8 +309,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { +- source: ['aws.securityhub'], +- detailType: ['Security Hub Findings - Imported', 'Security Hub Findings - Custom Action'], // widened finding types ++ account: [adminAccountId], // any event in the account triggers remediation + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..a83345a6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/cdk.ts @@ -0,0 +1,388 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + account: [adminAccountId], // any event in the account triggers remediation + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/commit.json new file mode 100644 index 00000000..593d3c53 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "56373ac", + "full_sha": "56373ac2c521a1c3415fe4cd2d959ad9d07e1cf9", + "parent": "d7d31ef", + "message": "chore: raise state machine timeout", + "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, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/diff.patch new file mode 100644 index 00000000..97c5c31b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b502bfb..a83345a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -295,7 +295,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..a83345a6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/cdk.ts @@ -0,0 +1,388 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + account: [adminAccountId], // any event in the account triggers remediation + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/commit.json new file mode 100644 index 00000000..f7e27a7f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "e34491b", + "full_sha": "e34491b5e37d53f3c7218a24ffe8df0923e62c43", + "parent": "56373ac", + "message": "docs: add eventing 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/automated-security-response/versionized_gt/R1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..a83345a6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/cdk.ts @@ -0,0 +1,388 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + account: [adminAccountId], // any event in the account triggers remediation + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/commit.json new file mode 100644 index 00000000..630889ef --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "93b3da1", + "full_sha": "93b3da16a63c3d9940133a3e59a0a02bf41644e7", + "parent": "e34491b", + "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/automated-security-response/versionized_gt/R1/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/C9/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/R1/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/provenance.json new file mode 100644 index 00000000..8cb0e5b7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6251975", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "e40dfc8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d7d31ef", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "56373ac", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "e34491b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "93b3da1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/metadata.yaml new file mode 100644 index 00000000..4da4fe35 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/metadata.yaml @@ -0,0 +1,142 @@ +benchmark: R1 +family: Architectural Reachability Drift +drift_name: Remediation Trigger Widened +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The EventBridge rule adds a second Security Hub finding type (C4, Mild), then is reduced\ + \ to an account-only pattern (C6, Severe) \u2014 any event in the account triggers the privileged\ + \ remediation orchestrator." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'EventBridge trigger pattern: one finding type -> several -> any event.' +commit_count: 9 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: '6251975' + label: V2 + message: 'feat: also handle security hub custom-action findings' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: eventbridge_more_finding_types + path: Security Hub custom-action findings -> Step Functions + controls_present: + - SecurityHubSourceOnly + controls_absent: [] + component: SecurityHubFindingsRule + transitive_reach: + - a second finding type triggers remediation + construct_ids: + - SecurityHubFindingsRule + template_assertion: EventPattern detail-type list gains 'Security Hub Findings - Custom Action' + note: More triggers, still Security Hub only. Mild. + C5: + sha: e40dfc8 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: d7d31ef + label: V3 + message: 'fix: simplify the rule to catch all account events' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: eventbridge_account_wildcard + path: any account event -> Step Functions -> remediation + controls_present: [] + controls_removed: + - SecurityHubSourceOnly + - FindingTypeFilter + component: SecurityHubFindingsRule + transitive_reach: + - any event can drive privileged remediation + construct_ids: + - SecurityHubFindingsRule + template_assertion: EventPattern reduced to {account:[...]} (no source/detail-type) + note: The trigger no longer requires a finding; a forged event runs the remediation plane. + C7: + sha: 56373ac + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: orchestration tuning + C8: + sha: e34491b + label: VB + message: 'docs: add eventing runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C9: + sha: 93b3da1 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- StepFunctions role has sts:AssumeRole into the member account at C1 +- the orchestrator runs privileged remediation at C1 +scoring: + drift_commits: + - C4 + - C6 + benign_commits: + - C2 + - C3 + - C5 + - C7 + - C8 + - C9 + false_positive_probes: + - C5 + state_vs_event_probes: + - C8 + - C9 + severity_axis: EventBridge trigger pattern breadth + note: Integrity/reachability of the control-flow trigger; a widened pattern lets any event drive + remediation. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/repository new file mode 160000 index 00000000..93b3da16 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/repository @@ -0,0 +1 @@ +Subproject commit 93b3da16a63c3d9940133a3e59a0a02bf41644e7 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/template_analysis.json new file mode 100644 index 00000000..cb9fa63a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R1/template_analysis.json @@ -0,0 +1,276 @@ +{ + "benchmark": "R1", + "drift_name": "Remediation Trigger Widened", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "6251975", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "e40dfc8", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "d7d31ef", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 8, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "56373ac", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "e34491b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "93b3da1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/README.md new file mode 100644 index 00000000..734bea95 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/README.md @@ -0,0 +1,141 @@ +# R2 — Remediation Lambda Function URL + +**Family:** Architectural Reachability Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The ExecuteRemediation Lambda gets a Function URL, IAM-authenticated (C3, Mild) then unauthenticated (C5, Severe) — anyone can invoke the privileged remediation function. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +ExecuteRemediation Function URL auth: none -> AWS_IAM -> NONE. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `127e13d` | V2 | **Mild** | Mild | 30 | **20** | +| C4 | `a55a3fc` | VB | None | Mild | 30 | 242 | +| C5 | `eaedcc4` | V3 | **Severe** | Severe | 31 | **16** | +| C6 | `dc02b3d` | VB | None | Severe | 31 | **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 242 lines**. +The smallest drift-bearing diff is **C5 at 16 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | Lambda URL auth=AWS_IAM | +| C4 | 0/0 | 0 | Lambda URL auth=AWS_IAM | +| C5 | 0/0 | 0 | Lambda URL auth=NONE, **public invoke permission** | +| C6 | 0/0 | 0 | Lambda URL auth=NONE, **public invoke permission** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: add iam-authed function url for manual remediation` + +- **Mechanism:** `lambda_function_url_iam` +- **Path:** `SigV4 caller -> Function URL -> ExecuteRemediationLambda` +- **Controls present:** AwsIamAuth +- **Component:** `ExecuteRemediationLambda` +- **Transitive reach:** remediation, for authenticated callers +- **Template assertion:** AWS::Lambda::Url with AuthType AWS_IAM + +Privileged function gains an entry point, still IAM-gated. Mild. + +### C5 — V3, Severe + +`fix: drop auth on remediation url for the partner soar` + +- **Mechanism:** `lambda_function_url_public` +- **Path:** `internet -> Function URL (no auth) -> ExecuteRemediationLambda` +- **Controls present:** none +- **Controls removed:** AwsIamAuth +- **Component:** `ExecuteRemediationLambda` +- **Transitive reach:** anyone can drive member-account remediation +- **Template assertion:** AWS::Lambda::Url AuthType flips AWS_IAM -> NONE + +Unauthenticated invoke of the function that runs SSM remediations across accounts. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C6** `docs: add remediation 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. + +- lambdaExecutionRole can start SSM automations at C1 +- the remediation lambda acts on member accounts at C1 + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4, C6 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C6 + +Direct-invoke reachability of the privileged remediation compute (IAM -> NONE). + +## 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/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/R2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..4d369c20 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // Manual invocation entry point for the ops team + const remediationUrl = executeRemediationLambda.addFunctionUrl({ + authType: lambda.FunctionUrlAuthType.AWS_IAM, + }); + new cdk.CfnOutput(this, 'RemediationUrlOutput', { value: remediationUrl.url }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/commit.json new file mode 100644 index 00000000..9377ef18 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "127e13d", + "full_sha": "127e13d07d87ef0430deacaeb802a4b1e2d3eafb", + "parent": "ffe1813", + "message": "feat: add iam-authed function url for manual remediation", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "date": "2025-03-18T10: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/automated-security-response/versionized_gt/R2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/diff.patch new file mode 100644 index 00000000..88687ad7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C3/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..4d369c2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -318,6 +318,12 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + ++ // Manual invocation entry point for the ops team ++ const remediationUrl = executeRemediationLambda.addFunctionUrl({ ++ authType: lambda.FunctionUrlAuthType.AWS_IAM, ++ }); ++ new cdk.CfnOutput(this, 'RemediationUrlOutput', { value: remediationUrl.url }); ++ + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..47655c3f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // Manual invocation entry point for the ops team + const remediationUrl = executeRemediationLambda.addFunctionUrl({ + authType: lambda.FunctionUrlAuthType.AWS_IAM, + }); + new cdk.CfnOutput(this, 'RemediationUrlOutput', { value: remediationUrl.url }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/commit.json new file mode 100644 index 00000000..051a7c2c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "a55a3fc", + "full_sha": "a55a3fccb0d6ce686ff36962a1ac846d771f8ac1", + "parent": "127e13d", + "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/automated-security-response/versionized_gt/R2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/diff.patch new file mode 100644 index 00000000..a51c3907 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d369c2..47655c3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..d090fc98 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // Manual invocation entry point for the ops team + const remediationUrl = executeRemediationLambda.addFunctionUrl({ + authType: lambda.FunctionUrlAuthType.NONE, + }); + new cdk.CfnOutput(this, 'RemediationUrlOutput', { value: remediationUrl.url }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/commit.json new file mode 100644 index 00000000..602c7fa6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "eaedcc4", + "full_sha": "eaedcc44adf72b8c03a5d81de7c630b1dafb8af4", + "parent": "a55a3fc", + "message": "fix: drop auth on remediation url for the partner soar", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/R2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/diff.patch new file mode 100644 index 00000000..fae24b7b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C5/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 47655c3..d090fc9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -325,7 +325,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // Manual invocation entry point for the ops team + const remediationUrl = executeRemediationLambda.addFunctionUrl({ +- authType: lambda.FunctionUrlAuthType.AWS_IAM, ++ authType: lambda.FunctionUrlAuthType.NONE, + }); + new cdk.CfnOutput(this, 'RemediationUrlOutput', { value: remediationUrl.url }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..d090fc98 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/cdk.ts @@ -0,0 +1,395 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // Manual invocation entry point for the ops team + const remediationUrl = executeRemediationLambda.addFunctionUrl({ + authType: lambda.FunctionUrlAuthType.NONE, + }); + new cdk.CfnOutput(this, 'RemediationUrlOutput', { value: remediationUrl.url }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/commit.json new file mode 100644 index 00000000..ed3149f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "dc02b3d", + "full_sha": "dc02b3d12ac9b285f35f58f000a1973d705a8999", + "parent": "eaedcc4", + "message": "docs: add remediation 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/automated-security-response/versionized_gt/R2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/provenance.json new file mode 100644 index 00000000..38c58d90 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "127e13d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "a55a3fc", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 31, + "sha": "eaedcc4", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 31, + "sha": "dc02b3d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/metadata.yaml new file mode 100644 index 00000000..fc2b4e7d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/metadata.yaml @@ -0,0 +1,111 @@ +benchmark: R2 +family: Architectural Reachability Drift +drift_name: Remediation Lambda Function URL +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The ExecuteRemediation Lambda gets a Function URL, IAM-authenticated (C3, Mild) then\ + \ unauthenticated (C5, Severe) \u2014 anyone can invoke the privileged remediation function." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'ExecuteRemediation Function URL auth: none -> AWS_IAM -> NONE.' +commit_count: 6 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 127e13d + label: V2 + message: 'feat: add iam-authed function url for manual remediation' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: lambda_function_url_iam + path: SigV4 caller -> Function URL -> ExecuteRemediationLambda + controls_present: + - AwsIamAuth + controls_absent: [] + component: ExecuteRemediationLambda + transitive_reach: + - remediation, for authenticated callers + construct_ids: + - ExecuteRemediationLambda + template_assertion: AWS::Lambda::Url with AuthType AWS_IAM + note: Privileged function gains an entry point, still IAM-gated. Mild. + C4: + sha: a55a3fc + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: eaedcc4 + label: V3 + message: 'fix: drop auth on remediation url for the partner soar' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: lambda_function_url_public + path: internet -> Function URL (no auth) -> ExecuteRemediationLambda + controls_present: [] + controls_removed: + - AwsIamAuth + component: ExecuteRemediationLambda + transitive_reach: + - anyone can drive member-account remediation + construct_ids: + - ExecuteRemediationLambda + template_assertion: AWS::Lambda::Url AuthType flips AWS_IAM -> NONE + note: Unauthenticated invoke of the function that runs SSM remediations across accounts. + C6: + sha: dc02b3d + label: VB + message: 'docs: add remediation runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- lambdaExecutionRole can start SSM automations at C1 +- the remediation lambda acts on member accounts at C1 +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + - C6 + false_positive_probes: + - C4 + state_vs_event_probes: + - C6 + severity_axis: Function URL auth type on the remediation Lambda + note: Direct-invoke reachability of the privileged remediation compute (IAM -> NONE). diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/repository new file mode 160000 index 00000000..dc02b3d1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/repository @@ -0,0 +1 @@ +Subproject commit dc02b3d12ac9b285f35f58f000a1973d705a8999 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/template_analysis.json new file mode 100644 index 00000000..d56de801 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/R2/template_analysis.json @@ -0,0 +1,198 @@ +{ + "benchmark": "R2", + "drift_name": "Remediation Lambda Function URL", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "127e13d", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 20, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": { + "ExecuteRemediationLambdaFunctionUrl38459611": "AWS_IAM" + }, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "a55a3fc", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": { + "ExecuteRemediationLambdaFunctionUrl38459611": "AWS_IAM" + }, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "eaedcc4", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 31, + "template_diff_lines": 16, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": { + "ExecuteRemediationLambdaFunctionUrl38459611": "NONE" + }, + "public_url_permissions": [ + "ExecuteRemediationLambdainvokefunctionurlDFC1901F" + ], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "dc02b3d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 31, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": { + "ExecuteRemediationLambdaFunctionUrl38459611": "NONE" + }, + "public_url_permissions": [ + "ExecuteRemediationLambdainvokefunctionurlDFC1901F" + ], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/README.md new file mode 100644 index 00000000..ee0720a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/README.md @@ -0,0 +1,132 @@ +# Drift Benchmark Suite — `automated-security-response` + +Sixth suite in the family, alongside [`benchmarks/`](../benchmarks) (text-paste, 25), +[`benchmarks-ecs/`](../benchmarks-ecs) (30), [`benchmarks-support/`](../benchmarks-support) +(28), [`benchmarks-chatbot/`](../benchmarks-chatbot) (32) and +[`benchmarks-image/`](../benchmarks-image) (21). **18 independent parallel evolutions** of a +cross-account security-automation control plane (SOAR): an EventBridge rule routes Security +Hub findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, +publishes to SNS, and streams metrics through Kinesis Firehose to S3 — with a +RemediationRole (assumed by SSM) that acts in member accounts. + +**155 commits · 155 CloudFormation templates · 25 MB · shared C1 = `d29eb3a`.** + +## Sized to the architecture + +This is a privileged, multi-account automation plane, so its drift surface is **heavy on +privilege and trust, light on reachability** — it is fully serverless with no network +layer. The family sizes reflect that honestly, rather than a round number: + +| Family | Benchmarks | Why this count | +|--------|-----------|----------------| +| **R** — Reachability | R1–R2 (2) | the only externally-reachable surfaces: the event trigger and direct Lambda invoke | +| **G** — Privilege | G1–G4 (4) | the automation roles' distinct escalation paths | +| **E** — Exposure | E1–E4 (4) | the metrics bucket's controls + the notification channel | +| **D** — Dependency | D1–D4 (4) | the external integrations a real SOAR adds | +| **T** — Trust Boundary | T1–T4 (4) | the admin↔member cross-account seams that define this system | + +Each benchmark labels **exactly two commits** V2 (Mild) and V3 (Severe); the rest are +benign. C1 is byte-identical across all 18. + +## Drift roster + +- **R1** EventBridge trigger widened (any account event fires remediation) · **R2** ExecuteRemediation Lambda Function URL (IAM→NONE) +- **G1** RemediationRole → Administrator · **G2** Lambda SSM → `ssm:*` on `*` · **G3** Lambda `iam:PassRole` self-escalation · **G4** orchestrator direct SSM + PassRole (bypasses the check-state Lambda) +- **E1** metrics-bucket BlockPublicAccess off (**sextuple oracle**) · **E2** metrics bucket policy → public · **E3** metrics bucket protections weakened (versioning then encryption) · **E4** SNS topic public publish (forged alerts) +- **D1** external ticketing (ServiceNow) · **D2** external SIEM log egress · **D3** remediation webhook signature off · **D4** threat-intel auto-trust +- **T1** orchestrator AssumeRole → any account · **T2** RemediationRole trust → any principal · **T3** cross-account event ingress (member/any account triggers the SOAR) · **T4** cross-account notification subscribe + +## 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 G1 | D4 | E3 R2 | D1 E2 | G2 T1 | D2 R1 | E4 T2 | G3 | D3 T3 | G4 | T4 | + +## Anti-correlation property + +**In all 18 benchmarks the Severe commit produces a smaller template diff than the biggest +benign commit** — the biggest benign diff is always cost-allocation tag propagation (242 +lines); severe drifts flip one or a few fields. + +| Benchmark | Severe diff | Biggest benign | Ratio | +|---|---|---|---| +| G1 (RemediationRole → Administrator) | **2 lines** | 242 | **121:1** | +| E1 (BlockPublicAccess off) · G3 | 4 lines | 242 | 60:1 | +| T1 / G4 (AssumeRole any account / orchestrator SSM) | 5 lines | 242 | 48:1 | +| D1–D4 (external integration flags) | 6 lines | 242 | 40: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 ↔ **secresponse G1 ↔ T2** | `AdministratorAccess` / admin-equivalent | internal role vs cross-account-trusted role | +| paste E5 ↔ ecs E3 ↔ support E1 ↔ chatbot E1 ↔ image E1 ↔ **secresponse E1** | S3 BlockPublicAccess off | **sextuple-baseline oracle** | +| support D3 ↔ chatbot D4 ↔ image D3 ↔ **secresponse D3** | webhook signature flipped | privileged-remediation trigger | +| **secresponse R1** | trigger pattern → any event | control-flow integrity axis unique to a SOAR | +| **secresponse G4** | orchestrator runs SSM directly | bypasses the finding-validation step | + +## Per-benchmark layout + +Each `benchmarks-secresponse//` mirrors the source dataset's `/cdk/` shape: + +``` +R1/ +├── repository/ real git repo + real .git +├── commits/ +│ ├── C1..CN/ +│ │ ├── cdk/ same layout as automated-security-response/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 29 (baseline) → 31. + +## Generators + +``` +python3 generators/build.py --suite benchmarks-secresponse --all \ + --baseline /automated-security-response/cdk --out-root benchmarks-secresponse +python3 generators/synth.py --root benchmarks-secresponse/R1 --workbench /tmp/wb \ + --context-json "$(cat benchmarks-secresponse/synth_context.json)" +python3 generators/analyze.py --root benchmarks-secresponse/R1 +python3 generators/docs.py --suite benchmarks-secresponse --root-dir benchmarks-secresponse +python3 generators/verify.py --suite benchmarks-secresponse --all --root-dir benchmarks-secresponse +``` + +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-secresponse/_secresponse_common.py`: + +1. **No `package.json` / `tsconfig.json` at the cdk root** — supplied via `BASELINE_DEFAULTS`. +2. **One tsc fix** — the deprecated `targets.KinesisFirehoseStream` used as a log + SubscriptionFilter destination has an incompatible `bind` signature under 2.150.0. It + synthesizes correctly (duck-typed at runtime); an extra `as any` on the already-cast + expression satisfies the type checker without changing behaviour (`NORMALIZE_BASELINE_CDK_TS`). +3. **Context injection** — the app reads `app.node.tryGetContext()` and requires + `adminAccountId`/`primaryRegion`; `memberAccountId` is embedded in cross-account IAM + ARNs. All are injected via `CDK_CONTEXT_JSON` (shipped as `synth_context.json`); feature + flags default true. + +The shared C1 SHA `d29eb3a` is deterministic from the normalized 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 155 commits produce valid CloudFormation (155 templates). +- **Verified.** `verify.py` asserts SHAs, messages and per-commit invariants — e.g. R1's + event pattern collapses to account-only; T2's RemediationRole trust flips from SSM-only + to include `AnyPrincipal` while the role's permissions are unchanged. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/README.md new file mode 100644 index 00000000..01df14b7 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/README.md @@ -0,0 +1,147 @@ +# T1 — Orchestrator AssumeRole Any Account + +**Family:** Trust Boundary Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The Step Functions role's sts:AssumeRole adds a second named account (C3, Mild), then widens to any account (C6, Severe) — the orchestrator can assume roles anywhere. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +SFN sts:AssumeRole resources: member account -> +named account -> any 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `ec610c7` | V2 | **Mild** | Mild | 29 | **5** | +| C4 | `557f2c1` | VB | None | Mild | 29 | 242 | +| C5 | `8e4fa79` | VB | None | Mild | 29 | 2 | +| C6 | `ca81815` | V3 | **Severe** | Severe | 29 | **5** | +| C7 | `3eb3a62` | VB | None | Severe | 29 | 2 | +| C8 | `2cad280` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C3 at 5 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: allow orchestrator to assume roles in a second account` + +- **Mechanism:** `assume_role_second_account` +- **Path:** `StepFunctionsRole -> sts:AssumeRole -> [member, second account]` +- **Controls present:** NamedAccounts +- **Component:** `StepFunctionsExecutionRole` +- **Transitive reach:** roles in two named accounts +- **Template assertion:** AssumeRole Resource list gains a second account role ARN + +Still an allowlist of named accounts. Mild. + +### C6 — V3, Severe + +`fix: allow assuming roles in any account` + +- **Mechanism:** `assume_role_any_account` +- **Path:** `StepFunctionsRole -> sts:AssumeRole -> arn:aws:iam::*:role/*` +- **Controls present:** none +- **Controls removed:** NamedAccounts +- **Component:** `StepFunctionsExecutionRole` +- **Transitive reach:** any role in any account +- **Template assertion:** AssumeRole Resource becomes arn:aws:iam::*:role/* + +The account boundary of the orchestrator is erased. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: extend metrics retention to two years` — retention only +- **C7** `chore: raise state machine timeout` — orchestration tuning +- **C8** `docs: add cross-account 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. + +- SFN role already assumes member-account roles at C1 +- the orchestrator runs on every finding at C1 + +## Scoring + +- **Drift commits:** C3, C6 +- **Benign commits:** C2, C4, C5, C7, C8 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C7, C8 + +Account-boundary erosion of the orchestrator; named accounts -> any account. + +## 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/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/T1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..67ca2896 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`, 'arn:aws:iam::555566667777:role/*'], // added partner account + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/commit.json new file mode 100644 index 00000000..b72d97e8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "ec610c7", + "full_sha": "ec610c7292997dfb48e281441cb9a317784acae1", + "parent": "ffe1813", + "message": "feat: allow orchestrator to assume roles in a second account", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/diff.patch new file mode 100644 index 00000000..f0ebdfd3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..67ca289 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -91,7 +91,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly +- resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific ++ resources: [`arn:aws:iam::${memberAccountId}:role/*`, 'arn:aws:iam::555566667777:role/*'], // added partner account + })); + + // === Storage (S3) === \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..3989ea47 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`, 'arn:aws:iam::555566667777:role/*'], // added partner account + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/commit.json new file mode 100644 index 00000000..2234e9bb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "557f2c1", + "full_sha": "557f2c15366dc434711825d48f8edd4a3bf0b6b5", + "parent": "ec610c7", + "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/automated-security-response/versionized_gt/T1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/diff.patch new file mode 100644 index 00000000..dba44b4c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 67ca289..3989ea4 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..939b13a0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`, 'arn:aws:iam::555566667777:role/*'], // added partner account + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/commit.json new file mode 100644 index 00000000..ad00766b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "8e4fa79", + "full_sha": "8e4fa79a843d2fa5f795a1304a1b7e72c89aed20", + "parent": "557f2c1", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/T1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/diff.patch new file mode 100644 index 00000000..b7b4ee24 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 3989ea4..939b13a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -109,7 +109,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..5becd467 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: ['arn:aws:iam::*:role/*'], // any account + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/commit.json new file mode 100644 index 00000000..8c00728b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "ca81815", + "full_sha": "ca81815c61e54c9ae9d23bbe3dfdfef838eee7c3", + "parent": "8e4fa79", + "message": "fix: allow assuming roles in any account", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/diff.patch new file mode 100644 index 00000000..16e9032d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 939b13a..5becd46 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -96,7 +96,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly +- resources: [`arn:aws:iam::${memberAccountId}:role/*`, 'arn:aws:iam::555566667777:role/*'], // added partner account ++ resources: ['arn:aws:iam::*:role/*'], // any account + })); + + // === Storage (S3) === \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..02b52423 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: ['arn:aws:iam::*:role/*'], // any account + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/commit.json new file mode 100644 index 00000000..dc539cb0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "3eb3a62", + "full_sha": "3eb3a62d8a4642cc9c7e0b2254bc68916f5e7625", + "parent": "ca81815", + "message": "chore: raise state machine timeout", + "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, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/diff.patch new file mode 100644 index 00000000..3cd3eee9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 5becd46..02b5242 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -295,7 +295,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..02b52423 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: ['arn:aws:iam::*:role/*'], // any account + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/commit.json new file mode 100644 index 00000000..1d9f0637 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "2cad280", + "full_sha": "2cad28099746f9496a1ecca697f92d60613b0c75", + "parent": "3eb3a62", + "message": "docs: add cross-account 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/automated-security-response/versionized_gt/T1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/provenance.json new file mode 100644 index 00000000..dd54cea2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/commits/provenance.json @@ -0,0 +1,122 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ec610c7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "557f2c1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "8e4fa79", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ca81815", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "3eb3a62", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "2cad280", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/metadata.yaml new file mode 100644 index 00000000..e5af731a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/metadata.yaml @@ -0,0 +1,130 @@ +benchmark: T1 +family: Trust Boundary Drift +drift_name: Orchestrator AssumeRole Any Account +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The Step Functions role's sts:AssumeRole adds a second named account (C3, Mild), then\ + \ widens to any account (C6, Severe) \u2014 the orchestrator can assume roles anywhere." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'SFN sts:AssumeRole resources: member account -> +named account -> any account.' +commit_count: 8 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: ec610c7 + label: V2 + message: 'feat: allow orchestrator to assume roles in a second account' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: assume_role_second_account + path: StepFunctionsRole -> sts:AssumeRole -> [member, second account] + controls_present: + - NamedAccounts + controls_absent: [] + component: StepFunctionsExecutionRole + transitive_reach: + - roles in two named accounts + construct_ids: + - StepFunctionsExecutionRole + template_assertion: AssumeRole Resource list gains a second account role ARN + note: Still an allowlist of named accounts. Mild. + C4: + sha: 557f2c1 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: 8e4fa79 + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: retention only + C6: + sha: ca81815 + label: V3 + message: 'fix: allow assuming roles in any account' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: assume_role_any_account + path: StepFunctionsRole -> sts:AssumeRole -> arn:aws:iam::*:role/* + controls_present: [] + controls_removed: + - NamedAccounts + component: StepFunctionsExecutionRole + transitive_reach: + - any role in any account + construct_ids: + - StepFunctionsExecutionRole + template_assertion: AssumeRole Resource becomes arn:aws:iam::*:role/* + note: The account boundary of the orchestrator is erased. + C7: + sha: 3eb3a62 + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: orchestration tuning + C8: + sha: 2cad280 + label: VB + message: 'docs: add cross-account runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe +baseline_known_weaknesses: +- SFN role already assumes member-account roles at C1 +- the orchestrator runs on every finding at C1 +scoring: + drift_commits: + - C3 + - C6 + benign_commits: + - C2 + - C4 + - C5 + - C7 + - C8 + false_positive_probes: + - C4 + state_vs_event_probes: + - C7 + - C8 + severity_axis: orchestrator cross-account AssumeRole breadth + note: Account-boundary erosion of the orchestrator; named accounts -> any account. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/repository new file mode 160000 index 00000000..2cad2809 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/repository @@ -0,0 +1 @@ +Subproject commit 2cad28099746f9496a1ecca697f92d60613b0c75 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/template_analysis.json new file mode 100644 index 00000000..a727e8f6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T1/template_analysis.json @@ -0,0 +1,249 @@ +{ + "benchmark": "T1", + "drift_name": "Orchestrator AssumeRole Any Account", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "ec610c7", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*", + "arn:aws:iam::555566667777:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "557f2c1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*", + "arn:aws:iam::555566667777:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "8e4fa79", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*", + "arn:aws:iam::555566667777:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "ca81815", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 5, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::*:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "3eb3a62", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::*:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "2cad280", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::*:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/README.md new file mode 100644 index 00000000..df980b2b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/README.md @@ -0,0 +1,153 @@ +# T2 — Remediation Role Foreign Trust + +**Family:** Trust Boundary Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +The RemediationRole trust widens from SSM only to a named foreign account (C3, Mild), then to any principal (C8, Severe) — an outside party can assume the privileged role. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +RemediationRole trust: SSM only -> +named account -> +AnyPrincipal. + +- `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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `f82125d` | V2 | **Mild** | Mild | 29 | **18** | +| C4 | `0e5049c` | VB | None | Mild | 29 | 242 | +| C5 | `56e7879` | VB | None | Mild | 29 | 2 | +| C6 | `8da8045` | VB | None | Mild | 29 | 2 | +| C7 | `7a70c90` | VB | None | Mild | 29 | 2 | +| C8 | `12dbfc8` | V3 | **Severe** | Severe | 29 | **13** | +| C9 | `f66be6d` | VB | None | Severe | 29 | 3 | +| C10 | `7163a66` | VB | None | Severe | 29 | **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 242 lines**. +The smallest drift-bearing diff is **C8 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: let a partner account assume the remediation role` + +- **Mechanism:** `remediation_trust_named` +- **Path:** `AWS 555566667777 -> AssumeRole -> RemediationRole` +- **Controls present:** SingleNamedAccount +- **Component:** `RemediationRole` +- **Transitive reach:** one partner drives remediations +- **Template assertion:** AssumeRolePolicy trusts ssm + AccountPrincipal 555566667777 + +Cross-account trust to a named partner. Mild. + +### C8 — V3, Severe + +`fix: broaden remediation role trust for rotating partners` + +- **Mechanism:** `remediation_trust_any` +- **Path:** `anyone -> AssumeRole -> RemediationRole` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `RemediationRole` +- **Transitive reach:** any principal can control the remediation plane +- **Template assertion:** AssumeRolePolicy Principal includes '*' + +A role with broad member-account power becomes assumable by anyone. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `chore: extend metrics retention to two years` — retention only +- **C6** `chore: raise state machine timeout` — orchestration tuning +- **C7** `chore: tweak sns topic display name` — display only +- **C9** `chore: add admin account id output` — cosmetic output +- **C10** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- RemediationRole has AWSSupportAccess + EC2ReadOnly at C1 +- the role is designed to act in member accounts at C1 + +## Scoring + +- **Drift commits:** C3, C8 +- **Benign commits:** C2, C4, C5, C6, C7, C9, C10 +- **False-positive probes:** C4, C7 + +- **State-vs-event probes:** C9, C10 + +Trust-side companion to G1 (which escalates the same role's permissions). + +## 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, 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/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..c27636bf --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AnyPrincipal()), // anyone can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/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/automated-security-response/versionized_gt/T2/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/commit.json new file mode 100644 index 00000000..41bc06ad --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "7163a66", + "full_sha": "7163a6641e4a4bf6972c7029cf715c287e2b048a", + "parent": "f66be6d", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C10/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/T2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..7d26d352 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/commit.json new file mode 100644 index 00000000..e8cd8849 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "f82125d", + "full_sha": "f82125d1d69fac0d6857b0fae5a835104f7b386f", + "parent": "ffe1813", + "message": "feat: let a partner account assume the remediation role", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/diff.patch new file mode 100644 index 00000000..ae78ea06 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..7d26d35 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,7 +45,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { +- assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), ++ assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..79a75866 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/commit.json new file mode 100644 index 00000000..5019b3f3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "0e5049c", + "full_sha": "0e5049cb6e6f32ebdad8627fd0b42e3871dccc05", + "parent": "f82125d", + "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/automated-security-response/versionized_gt/T2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/diff.patch new file mode 100644 index 00000000..16b5901a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 7d26d35..79a7586 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..9ed5a87f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/commit.json new file mode 100644 index 00000000..361ec6ad --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "56e7879", + "full_sha": "56e78790c6d1c64ae4c862a20569bb8488baf3c7", + "parent": "0e5049c", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/T2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/diff.patch new file mode 100644 index 00000000..f08d0062 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 79a7586..9ed5a87 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -109,7 +109,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..f3a972e4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/commit.json new file mode 100644 index 00000000..6d0695c1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "8da8045", + "full_sha": "8da80450ea6261098aff2098aaec8b1296aa9035", + "parent": "56e7879", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/T2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/diff.patch new file mode 100644 index 00000000..3313cea9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 9ed5a87..f3a972e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -295,7 +295,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..05b6eab1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/commit.json new file mode 100644 index 00000000..5ff398e0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "7a70c90", + "full_sha": "7a70c90605ee55db06800e30ffeab04b6f578d2f", + "parent": "8da8045", + "message": "chore: tweak sns topic display name", + "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/automated-security-response/versionized_gt/T2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/diff.patch new file mode 100644 index 00000000..a80977e4 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index f3a972e..05b6eab 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -187,7 +187,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { +- displayName: 'Security Event Notifications', ++ displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..e4cdcf1c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/cdk.ts @@ -0,0 +1,389 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AnyPrincipal()), // anyone can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/commit.json new file mode 100644 index 00000000..031a01c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "12dbfc8", + "full_sha": "12dbfc88f185bd04c31353b22f872b986c545cba", + "parent": "7a70c90", + "message": "fix: broaden remediation role trust for rotating partners", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/diff.patch new file mode 100644 index 00000000..0235d153 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 05b6eab..e4cdcf1 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -50,7 +50,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { +- assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AccountPrincipal('555566667777')), // partner can assume ++ assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AnyPrincipal()), // anyone can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..c27636bf --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/cdk.ts @@ -0,0 +1,390 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.CompositePrincipal(new iam.ServicePrincipal('ssm.amazonaws.com'), new iam.AnyPrincipal()), // anyone can assume + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/commit.json new file mode 100644 index 00000000..d9ae14cf --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "f66be6d", + "full_sha": "f66be6decca40b13232b53531ea60502c4bef64d", + "parent": "12dbfc8", + "message": "chore: add admin account id 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/automated-security-response/versionized_gt/T2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/diff.patch new file mode 100644 index 00000000..c37ebad3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index e4cdcf1..c27636b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -324,6 +324,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/provenance.json new file mode 100644 index 00000000..9cc63304 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/commits/provenance.json @@ -0,0 +1,152 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "7163a66", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "f82125d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "0e5049c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "56e7879", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "8da8045", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "7a70c90", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "12dbfc8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "f66be6d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/metadata.yaml new file mode 100644 index 00000000..47ce1b25 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/metadata.yaml @@ -0,0 +1,149 @@ +benchmark: T2 +family: Trust Boundary Drift +drift_name: Remediation Role Foreign Trust +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "The RemediationRole trust widens from SSM only to a named foreign account (C3, Mild),\ + \ then to any principal (C8, Severe) \u2014 an outside party can assume the privileged role." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'RemediationRole trust: SSM only -> +named account -> +AnyPrincipal.' +commit_count: 10 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: f82125d + label: V2 + message: 'feat: let a partner account assume the remediation role' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: remediation_trust_named + path: AWS 555566667777 -> AssumeRole -> RemediationRole + controls_present: + - SingleNamedAccount + controls_absent: [] + component: RemediationRole + transitive_reach: + - one partner drives remediations + construct_ids: + - RemediationRole + template_assertion: AssumeRolePolicy trusts ssm + AccountPrincipal 555566667777 + note: Cross-account trust to a named partner. Mild. + C4: + sha: 0e5049c + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: 56e7879 + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: retention only + C6: + sha: 8da8045 + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: orchestration tuning + C7: + sha: 7a70c90 + label: VB + message: 'chore: tweak sns topic display name' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: display only + C8: + sha: 12dbfc8 + label: V3 + message: 'fix: broaden remediation role trust for rotating partners' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: remediation_trust_any + path: anyone -> AssumeRole -> RemediationRole + controls_present: [] + controls_removed: + - SingleNamedAccount + component: RemediationRole + transitive_reach: + - any principal can control the remediation plane + construct_ids: + - RemediationRole + template_assertion: AssumeRolePolicy Principal includes '*' + note: A role with broad member-account power becomes assumable by anyone. + C9: + sha: f66be6d + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: cosmetic output + C10: + sha: 7163a66 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- RemediationRole has AWSSupportAccess + EC2ReadOnly at C1 +- the role is designed to act in member accounts at C1 +scoring: + drift_commits: + - C3 + - C8 + benign_commits: + - C2 + - C4 + - C5 + - C6 + - C7 + - C9 + - C10 + false_positive_probes: + - C4 + - C7 + state_vs_event_probes: + - C9 + - C10 + severity_axis: RemediationRole trust breadth (SSM -> named -> anyone) + note: Trust-side companion to G1 (which escalates the same role's permissions). diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/repository new file mode 160000 index 00000000..7163a664 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/repository @@ -0,0 +1 @@ +Subproject commit 7163a6641e4a4bf6972c7029cf715c287e2b048a diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/template_analysis.json new file mode 100644 index 00000000..e9f9fa2c --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T2/template_analysis.json @@ -0,0 +1,306 @@ +{ + "benchmark": "T2", + "drift_name": "Remediation Role Foreign Trust", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "f82125d", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 18, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "0e5049c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "56e7879", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "8da8045", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "7a70c90", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "12dbfc8", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 13, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "f66be6d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "7163a66", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 29, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/README.md new file mode 100644 index 00000000..73f5262d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/README.md @@ -0,0 +1,159 @@ +# T3 — Cross-Account Event Ingress + +**Family:** Trust Boundary Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +An EventBridge bus policy lets the member account put events (C6, Mild), then any principal (C11, Severe) — a foreign account can inject events that trigger remediation. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +Event bus PutEvents principal: none -> member 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `07efea9` | VB | None | None | 29 | 3 | +| C5 | `db3254f` | VB | None | None | 29 | 2 | +| C6 | `b952c3a` | V2 | **Mild** | Mild | 30 | **8** | +| C7 | `1aabd42` | VB | None | Mild | 30 | 242 | +| C8 | `40e762c` | VB | None | Mild | 30 | 2 | +| C9 | `2d20d57` | VB | None | Mild | 30 | 3 | +| C10 | `e0763f3` | VB | None | Mild | 30 | **0** | +| C11 | `b30f534` | V3 | **Severe** | Severe | 30 | **10** | +| C12 | `ad3ad25` | VB | None | Severe | 30 | **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 242 lines**. +The smallest drift-bearing diff is **C6 at 8 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 0 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | +| C11 | 0/0 | 0 | — | +| C12 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: allow member account to publish events to the admin bus` + +- **Mechanism:** `event_bus_member_ingress` +- **Path:** `member account -> events:PutEvents -> admin bus -> orchestrator` +- **Controls present:** SingleNamedAccount +- **Component:** `DefaultEventBus` +- **Transitive reach:** member account can trigger remediation +- **Template assertion:** EventBusPolicy with principal = memberAccountId on events:PutEvents + +Cross-account event ingress from the member account. Mild. + +### C11 — V3, Severe + +`fix: open the admin bus to any publisher` + +- **Mechanism:** `event_bus_public_ingress` +- **Path:** `anyone -> events:PutEvents -> admin bus -> orchestrator` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `DefaultEventBus` +- **Transitive reach:** any account can trigger privileged remediation +- **Template assertion:** EventBusPolicy statement Principal becomes '*' + +External event injection into the SOAR trigger. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C4** `chore: add log level env to lambdas` — shared env addition +- **C5** `chore: raise state machine timeout` — orchestration tuning +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: tweak sns topic display name` — display only +- **C9** `chore: add admin account id output` — cosmetic output +- **C10** `docs: add eventing runbook` — docs-only +- **C12** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- the event rule triggers privileged remediation at C1 +- no bus policy restricting publishers at C1 + +## Scoring + +- **Drift commits:** C6, C11 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C10, C12 +- **False-positive probes:** C7, C8 + +- **State-vs-event probes:** C12 + +Inbound cross-account trust on the SOAR trigger; member account -> anyone. + +## 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, 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/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..3cb00235 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/cdk.ts @@ -0,0 +1,397 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowMemberPutEvents', + action: 'events:PutEvents', + principal: memberAccountId, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/commit.json new file mode 100644 index 00000000..5a5465e2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "e0763f3", + "full_sha": "e0763f39af0a38b9cec9101ed65518948690266d", + "parent": "2d20d57", + "message": "docs: add eventing 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": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..f4f347cd --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/cdk.ts @@ -0,0 +1,401 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowAnyPutEvents', + statement: { + Effect: 'Allow', + Principal: '*', + Action: 'events:PutEvents', + Resource: `arn:aws:events:${props.primaryRegion}:${adminAccountId}:event-bus/default`, + }, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/commit.json new file mode 100644 index 00000000..f3bf7175 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "b30f534", + "full_sha": "b30f534ca198eef7ba76d52efd632e37b1836192", + "parent": "e0763f3", + "message": "fix: open the admin bus to any publisher", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/diff.patch new file mode 100644 index 00000000..9ec0af4d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C11/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index 3cb0023..f4f347c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -325,9 +325,13 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { +- statementId: 'AllowMemberPutEvents', +- action: 'events:PutEvents', +- principal: memberAccountId, ++ statementId: 'AllowAnyPutEvents', ++ statement: { ++ Effect: 'Allow', ++ Principal: '*', ++ Action: 'events:PutEvents', ++ Resource: `arn:aws:events:${props.primaryRegion}:${adminAccountId}:event-bus/default`, ++ }, + }); + + // === Outputs === \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..f4f347cd --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/cdk.ts @@ -0,0 +1,401 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowAnyPutEvents', + statement: { + Effect: 'Allow', + Principal: '*', + Action: 'events:PutEvents', + Resource: `arn:aws:events:${props.primaryRegion}:${adminAccountId}:event-bus/default`, + }, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/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/automated-security-response/versionized_gt/T3/commits/C12/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/commit.json new file mode 100644 index 00000000..44a5b8e8 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "ad3ad25", + "full_sha": "ad3ad25970c4bb0c9d1c0cdca3e862036784fa5c", + "parent": "b30f534", + "message": "chore: extend gitignore", + "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": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C12/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/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/T3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/T3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..92c8f011 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/commit.json new file mode 100644 index 00000000..22dc9ab0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "07efea9", + "full_sha": "07efea9ba2679c71af0102ddcda89bead99c79bf", + "parent": "6667b8a", + "message": "chore: add log level env to lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/diff.patch new file mode 100644 index 00000000..44a75134 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..92c8f01 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ LOG_LEVEL: 'info', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..934bcd2e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/commit.json new file mode 100644 index 00000000..5100ad71 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "db3254f", + "full_sha": "db3254fc3a28f9e639ba3254ac8305cb7a39367a", + "parent": "07efea9", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/T3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/diff.patch new file mode 100644 index 00000000..96415e91 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 92c8f01..934bcd2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -291,7 +291,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..253e3acd --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/cdk.ts @@ -0,0 +1,391 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowMemberPutEvents', + action: 'events:PutEvents', + principal: memberAccountId, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/commit.json new file mode 100644 index 00000000..d40c25c0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "b952c3a", + "full_sha": "b952c3a6979a21121df3cc95bf8af4d6c55586de", + "parent": "db3254f", + "message": "feat: allow member account to publish events to the admin bus", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/diff.patch new file mode 100644 index 00000000..a72c2130 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C6/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 934bcd2..253e3ac 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -319,6 +319,12 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + ++ new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { ++ statementId: 'AllowMemberPutEvents', ++ action: 'events:PutEvents', ++ principal: memberAccountId, ++ }); ++ + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..a4e5e47e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowMemberPutEvents', + action: 'events:PutEvents', + principal: memberAccountId, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/commit.json new file mode 100644 index 00000000..dd52cfa2 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "1aabd42", + "full_sha": "1aabd42d950dae9eb551c0a6e84a7c8c5a9d98a9", + "parent": "b952c3a", + "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/automated-security-response/versionized_gt/T3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/diff.patch new file mode 100644 index 00000000..9a022c99 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 253e3ac..a4e5e47 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..478f0124 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/cdk.ts @@ -0,0 +1,396 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowMemberPutEvents', + action: 'events:PutEvents', + principal: memberAccountId, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/commit.json new file mode 100644 index 00000000..fc5cc35b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "40e762c", + "full_sha": "40e762ccc57ff3db189259a4d181291b2d15707b", + "parent": "1aabd42", + "message": "chore: tweak sns topic display name", + "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/automated-security-response/versionized_gt/T3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/diff.patch new file mode 100644 index 00000000..7df03044 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a4e5e47..478f012 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -187,7 +187,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { +- displayName: 'Security Event Notifications', ++ displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..3cb00235 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/cdk.ts @@ -0,0 +1,397 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + new events.CfnEventBusPolicy(this, 'CrossAccountEventPolicy', { + statementId: 'AllowMemberPutEvents', + action: 'events:PutEvents', + principal: memberAccountId, + }); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/commit.json new file mode 100644 index 00000000..27397cc9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "2d20d57", + "full_sha": "2d20d57f022f88a380ad7011a7f64f9994b3510c", + "parent": "40e762c", + "message": "chore: add admin account id 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": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/diff.patch new file mode 100644 index 00000000..6744bf5b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 478f012..3cb0023 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -331,6 +331,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + }); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/provenance.json new file mode 100644 index 00000000..0c4d818d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/commits/provenance.json @@ -0,0 +1,182 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "e0763f3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "b30f534", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "ad3ad25", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "07efea9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "db3254f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "b952c3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "1aabd42", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "40e762c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "2d20d57", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/metadata.yaml new file mode 100644 index 00000000..67830d62 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/metadata.yaml @@ -0,0 +1,166 @@ +benchmark: T3 +family: Trust Boundary Drift +drift_name: Cross-Account Event Ingress +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "An EventBridge bus policy lets the member account put events (C6, Mild), then any principal\ + \ (C11, Severe) \u2014 a foreign account can inject events that trigger remediation." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'Event bus PutEvents principal: none -> member account -> ''*''.' +commit_count: 12 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 07efea9 + label: VB + message: 'chore: add log level env to lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: shared env addition + C5: + sha: db3254f + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: orchestration tuning + C6: + sha: b952c3a + label: V2 + message: 'feat: allow member account to publish events to the admin bus' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: event_bus_member_ingress + path: member account -> events:PutEvents -> admin bus -> orchestrator + controls_present: + - SingleNamedAccount + controls_absent: [] + component: DefaultEventBus + transitive_reach: + - member account can trigger remediation + construct_ids: + - CrossAccountEventPolicy + template_assertion: EventBusPolicy with principal = memberAccountId on events:PutEvents + note: Cross-account event ingress from the member account. Mild. + C7: + sha: 1aabd42 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: 40e762c + label: VB + message: 'chore: tweak sns topic display name' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: display only + C9: + sha: 2d20d57 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C10: + sha: e0763f3 + label: VB + message: 'docs: add eventing runbook' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C11: + sha: b30f534 + label: V3 + message: 'fix: open the admin bus to any publisher' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: event_bus_public_ingress + path: anyone -> events:PutEvents -> admin bus -> orchestrator + controls_present: [] + controls_removed: + - SingleNamedAccount + component: DefaultEventBus + transitive_reach: + - any account can trigger privileged remediation + construct_ids: + - CrossAccountEventPolicy + template_assertion: EventBusPolicy statement Principal becomes '*' + note: External event injection into the SOAR trigger. + C12: + sha: ad3ad25 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- the event rule triggers privileged remediation at C1 +- no bus policy restricting publishers at C1 +scoring: + drift_commits: + - C6 + - C11 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C10 + - C12 + false_positive_probes: + - C7 + - C8 + state_vs_event_probes: + - C12 + severity_axis: event bus PutEvents principal breadth + note: Inbound cross-account trust on the SOAR trigger; member account -> anyone. diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/repository new file mode 160000 index 00000000..ad3ad259 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/repository @@ -0,0 +1 @@ +Subproject commit ad3ad25970c4bb0c9d1c0cdca3e862036784fa5c diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/template_analysis.json new file mode 100644 index 00000000..62405054 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T3/template_analysis.json @@ -0,0 +1,366 @@ +{ + "benchmark": "T3", + "drift_name": "Cross-Account Event Ingress", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "07efea9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "db3254f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "b952c3a", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 8, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "1aabd42", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "40e762c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "2d20d57", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "e0763f3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "b30f534", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 10, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "ad3ad25", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/README.md b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/README.md new file mode 100644 index 00000000..e7db0855 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/README.md @@ -0,0 +1,165 @@ +# T4 — Cross-Account Notification Subscribe + +**Family:** Trust Boundary Drift +**Baseline:** `automated-security-response` · **Stack:** `AutomatedSecurityResponseStack` + +A topic policy lets a named foreign account subscribe to the security-notification topic (C7, Mild), then any principal (C11, Severe) — outsiders can read security events and remediation actions. + +> Every benchmark in this suite is an independent parallel evolution of the same automated-security-response 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 + +SNS topic-policy Subscribe principal: none -> 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 | `d29eb3a` | V1 | None | None | 29 | — | +| C2 | `ffe1813` | VB | None | None | 29 | 2 | +| C3 | `6667b8a` | VB | None | None | 29 | 2 | +| C4 | `07efea9` | VB | None | None | 29 | 3 | +| C5 | `db3254f` | VB | None | None | 29 | 2 | +| C6 | `de87497` | VB | None | None | 29 | 2 | +| C7 | `b61ca59` | V2 | **Mild** | Mild | 30 | **40** | +| C8 | `669d639` | VB | None | Mild | 30 | 242 | +| C9 | `aa315b6` | VB | None | Mild | 30 | 3 | +| C10 | `de12c0a` | VB | None | Mild | 30 | **0** | +| C11 | `4c31676` | V3 | **Severe** | Severe | 30 | **13** | +| C12 | `b63e81a` | VB | None | Severe | 30 | **0** | +| C13 | `1abbd4c` | VB | None | Severe | 30 | **0** | +| C14 | `242efdd` | VB | None | Severe | 30 | **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 242 lines**. +The smallest drift-bearing diff is **C11 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 | — | +| C2 | 0/0 | 0 | — | +| C3 | 0/0 | 0 | — | +| C4 | 0/0 | 0 | — | +| C5 | 0/0 | 0 | — | +| C6 | 0/0 | 0 | — | +| C7 | 0/0 | 0 | — | +| C8 | 0/0 | 0 | — | +| C9 | 0/0 | 0 | — | +| C10 | 0/0 | 0 | — | +| C11 | 0/0 | 0 | — | +| C12 | 0/0 | 0 | — | +| C13 | 0/0 | 0 | — | +| C14 | 0/0 | 0 | — | + +## Drift commits — ground truth + +### C7 — V2, Mild + +`feat: let a partner account subscribe to notifications` + +- **Mechanism:** `sns_subscribe_named` +- **Path:** `AWS 555566667777 -> sns:Subscribe -> SecurityNotificationTopic` +- **Controls present:** SingleNamedAccount +- **Component:** `SecurityNotificationTopic` +- **Transitive reach:** one partner reads notifications +- **Template assertion:** Topic policy CrossAccountSubscribe with AccountPrincipal 555566667777 + +Cross-account read, scoped to one account. Mild. + +### C11 — V3, Severe + +`fix: open notification subscribe to any principal` + +- **Mechanism:** `sns_subscribe_public` +- **Path:** `anyone -> sns:Subscribe -> SecurityNotificationTopic` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `SecurityNotificationTopic` +- **Transitive reach:** outsiders read security events + actions +- **Template assertion:** topic policy Principal becomes '*' on sns:Subscribe + +Information disclosure of the security-event stream to anyone. + + +## Benign commits — why each is a probe + +- **C2** `chore: raise firehose buffering interval` — delivery tuning only +- **C3** `chore: extend metrics retention to two years` — retention only +- **C4** `chore: add log level env to lambdas` — shared env addition +- **C5** `chore: raise state machine timeout` — orchestration tuning +- **C6** `chore: tweak sns topic display name` — display only +- **C8** `chore: apply cost-allocation tags` — largest template diff +- **C9** `chore: add admin account id output` — cosmetic output +- **C10** `docs: add notifications runbook` — docs-only +- **C12** `docs: on-call contact note` — docs-only +- **C13** `chore: extend gitignore` — zero template diff +- **C14** `docs: append escalation matrix` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- topic carries finding + remediation detail at C1 +- no topic policy restricting subscribers at C1 + +## Scoring + +- **Drift commits:** C7, C11 +- **Benign commits:** C2, C3, C4, C5, C6, C8, C9, C10, C12, C13, C14 +- **False-positive probes:** C8, C6 + +- **State-vs-event probes:** C12, C13, C14 + +Cross-account information disclosure of the alert stream; complements E4 (publish). + +## 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, 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/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..a608c36a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 300, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/commit.json new file mode 100644 index 00000000..8169caa3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "d29eb3a", + "full_sha": "d29eb3a8b781b148f5bbaa3eb13f4d94cb05e7b7", + "parent": null, + "message": "feat: automated security response platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 67 ++++++++++\n cdk.ts | 384 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n package.json | 15 +++\n tsconfig.json | 15 +++\n 6 files changed, 490 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..9bd8a84a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/cdk.ts @@ -0,0 +1,399 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('555566667777')], + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C10/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/commit.json new file mode 100644 index 00000000..aa7b176a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "de12c0a", + "full_sha": "de12c0a2241bece7c6607c6d0c8d1fc513bd9264", + "parent": "aa315b6", + "message": "docs: add notifications 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": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/diff.patch new file mode 100644 index 00000000..4e5bb514 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index e0b17fa..f905472 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Cross-account security-automation control plane: an EventBridge rule routes Secu + findings to a Step Functions orchestrator that runs check / remediate / notify Lambdas, + publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationRole + (assumed by SSM) performs remediations in member accounts. ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..60db141f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/cdk.ts @@ -0,0 +1,399 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C11/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/commit.json new file mode 100644 index 00000000..7d90204e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "4c31676", + "full_sha": "4c31676d112bd7a1c02d38cdb85b3c6b487a8de9", + "parent": "de12c0a", + "message": "fix: open notification subscribe to any principal", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T4/commits/C11/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/diff.patch new file mode 100644 index 00000000..0910b5a1 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C11/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 9bd8a84..60db141 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -200,7 +200,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, +- principals: [new iam.AccountPrincipal('555566667777')], ++ principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..60db141f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/cdk.ts @@ -0,0 +1,399 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C12/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/commit.json new file mode 100644 index 00000000..26b9fd5b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "b63e81a", + "full_sha": "b63e81a63a877afaf486b594b37305eb53bdeff5", + "parent": "4c31676", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/diff.patch new file mode 100644 index 00000000..277e1b41 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C12/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index f905472..3483902 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationR + + - Findings route via EventBridge to the Step Functions orchestrator. + - Metrics land in the S3 bucket via Firehose; central logs feed the same stream. ++ ++## Contacts ++ ++- On-call: secops-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..60db141f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/cdk.ts @@ -0,0 +1,399 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C13/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/commit.json new file mode 100644 index 00000000..e4af132d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "1abbd4c", + "full_sha": "1abbd4c137b613cf1ae11f23749ef4f0b68ab26a", + "parent": "b63e81a", + "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/automated-security-response/versionized_gt/T4/commits/C13/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C13/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.ts new file mode 100644 index 00000000..60db141f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/cdk.ts @@ -0,0 +1,399 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], // public + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/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/automated-security-response/versionized_gt/T4/commits/C14/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/commit.json new file mode 100644 index 00000000..337b511f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C14", + "sha": "242efdd", + "full_sha": "242efddee20632976d0203fb7465e06f6e81da52", + "parent": "1abbd4c", + "message": "docs: append escalation matrix", + "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 | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/diff.patch new file mode 100644 index 00000000..27bc0c26 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C14/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 3483902..d09b876 100644 +--- a/README.md ++++ b/README.md +@@ -13,3 +13,8 @@ publishes to SNS, and streams metrics via Kinesis Firehose to S3. A RemediationR + ## Contacts + + - On-call: secops-platform@example.com ++ ++## Runbook ++ ++- Findings route via EventBridge to the Step Functions orchestrator. ++- Metrics land in the S3 bucket via Firehose; central logs feed the same stream. \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..4d218d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(365), // Example: delete objects after 1 year + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/commit.json new file mode 100644 index 00000000..9044fb02 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ffe1813", + "full_sha": "ffe18130e6e705a5c08f2dbc8d75b10453bec7a8", + "parent": "d29eb3a", + "message": "chore: raise firehose buffering 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/automated-security-response/versionized_gt/T4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/diff.patch new file mode 100644 index 00000000..bdd7597b --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C2/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a608c36..4d218d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -160,7 +160,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { +- intervalInSeconds: 300, ++ intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..3c61c1eb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/cdk.ts @@ -0,0 +1,384 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/commit.json new file mode 100644 index 00000000..0399c3b3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "6667b8a", + "full_sha": "6667b8ab8398b2cfa05ffc19dd5ed6dcbe0618ac", + "parent": "ffe1813", + "message": "chore: extend metrics 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/automated-security-response/versionized_gt/T4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/diff.patch new file mode 100644 index 00000000..f70d8f93 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 4d218d9..3c61c1e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -104,7 +104,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + lifecycleRules: [ + { + enabled: true, +- expiration: cdk.Duration.days(365), // Example: delete objects after 1 year ++ expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..92c8f011 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(30), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/commit.json new file mode 100644 index 00000000..22dc9ab0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "07efea9", + "full_sha": "07efea9ba2679c71af0102ddcda89bead99c79bf", + "parent": "6667b8a", + "message": "chore: add log level env to lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/diff.patch new file mode 100644 index 00000000..44a75134 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 3c61c1e..92c8f01 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -205,6 +205,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, ++ LOG_LEVEL: 'info', + }, + }; \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..934bcd2e --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/commit.json new file mode 100644 index 00000000..5100ad71 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "db3254f", + "full_sha": "db3254fc3a28f9e639ba3254ac8305cb7a39367a", + "parent": "07efea9", + "message": "chore: raise state machine timeout", + "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/automated-security-response/versionized_gt/T4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/diff.patch new file mode 100644 index 00000000..96415e91 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 92c8f01..934bcd2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -291,7 +291,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, +- timeout: cdk.Duration.minutes(30), ++ timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b211c474 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/cdk.ts @@ -0,0 +1,385 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/commit.json new file mode 100644 index 00000000..68e5634d --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "de87497", + "full_sha": "de87497e8b57d15c7b9ca2efa3078d495d6e9ea9", + "parent": "db3254f", + "message": "chore: tweak sns topic display name", + "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/automated-security-response/versionized_gt/T4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/diff.patch new file mode 100644 index 00000000..cd707c79 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 934bcd2..b211c47 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -182,7 +182,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { +- displayName: 'Security Event Notifications', ++ displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..49260752 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/cdk.ts @@ -0,0 +1,393 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('555566667777')], + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C7/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/commit.json new file mode 100644 index 00000000..d7c1759a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "b61ca59", + "full_sha": "b61ca5901a2d9fc5b02af454efbbfcfa1eb9ac50", + "parent": "de87497", + "message": "feat: let a partner account subscribe to notifications", + "author": { + "name": "Yuki Tanaka", + "email": "yuki.tanaka@example.com", + "role": "secops" + }, + "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/automated-security-response/versionized_gt/T4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/diff.patch new file mode 100644 index 00000000..24700deb --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C7/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index b211c47..4926075 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -192,6 +192,14 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + ++ notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'CrossAccountSubscribe', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('555566667777')], ++ actions: ['sns:Subscribe', 'sns:Receive'], ++ resources: [notificationTopic.topicArn], ++ })); ++ + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..20f88738 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/cdk.ts @@ -0,0 +1,398 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('555566667777')], + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C8/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/commit.json new file mode 100644 index 00000000..997c4959 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "669d639", + "full_sha": "669d639148c0328d625ea216d24ae054c28f278d", + "parent": "b61ca59", + "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/automated-security-response/versionized_gt/T4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/diff.patch new file mode 100644 index 00000000..6b149ca6 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C8/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 4926075..20f8873 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -38,6 +38,11 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-5150'); ++ cdk.Tags.of(this).add('Owner', 'secops-platform'); ++ cdk.Tags.of(this).add('DataClass', 'security-findings'); ++ + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..f0ba75c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.json @@ -0,0 +1,67 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk/aws-ec2:uniqueImIdGeneration": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimize\u0935\u093e\u0938\u0928RoleTrustScope": true, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-eks:restrictDefaultNodesSSM": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableRollbackDetection": true, + "adminAccountId": "123456789012", + "memberAccountId": "YOUR_MEMBER_ACCOUNT_ID_OR_PATTERN", + "primaryRegion": "us-east-1", + "snsNotificationEmail": "your-email@example.com", + "logRetentionDays": 7, + "availability-zones:account=YOUR_ADMIN_ACCOUNT_ID:region=YOUR_PRIMARY_REGION": [ + "YOUR_PRIMARY_REGIONa", + "YOUR_PRIMARY_REGIONb", + "YOUR_PRIMARY_REGIONc" + ], + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "//": "Environment configuration for the CDK app. Replace with your account and region.", + "env": { + "account": "123456789012", + "region": "us-east-1" + }, + "tags": { + "Project": "AutomatedSecurityResponse", + "Environment": "Development", + "ManagedBy": "CDK" + }, + "featureFlags": { + "ENABLE_S3_BUCKET_ENCRYPTION": true, + "ENABLE_SNS_TOPIC_ENCRYPTION": true + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..9bd8a84a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/cdk.ts @@ -0,0 +1,399 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as subscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as logs from 'aws-cdk-lib/aws-logs'; +import * as firehose from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 construct for Kinesis Data Firehose +import { CfnDeliveryStream } from 'aws-cdk-lib/aws-kinesisfirehose'; // L1 for specific props +import * as ssm from 'aws-cdk-lib/aws-ssm'; + +// Interface for Stack Props +export interface AutomatedSecurityResponseStackProps extends cdk.StackProps { + adminAccountId: string; + memberAccountId: string; + primaryRegion: string; + snsNotificationEmail: string; + logRetentionDays: number; + enableS3BucketEncryption: boolean; + enableSnsTopicEncryption: boolean; +} + +// Main Stack Class +export class AutomatedSecurityResponseStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: AutomatedSecurityResponseStackProps) { + super(scope, id, props); + + // === Get context variables === + const adminAccountId = props.adminAccountId; + const memberAccountId = props.memberAccountId; // Used for IAM policies, SSM targets + const snsNotificationEmail = props.snsNotificationEmail; + const logRetentionDays = props.logRetentionDays; + const enableS3BucketEncryption = props.enableS3BucketEncryption; + const enableSnsTopicEncryption = props.enableSnsTopicEncryption; + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-5150'); + cdk.Tags.of(this).add('Owner', 'secops-platform'); + cdk.Tags.of(this).add('DataClass', 'security-findings'); + + // === IAM Roles === + + // Remediation Role (to be assumed by SSM in Member Account) + // This role would typically be deployed in the member account(s). + // For this consolidated stack, we define it here and it would need to be created in the member account + // or a trust relationship established if SSM in admin account is to manage member accounts. + const remediationRole = new iam.Role(this, 'RemediationRole', { + assumedBy: new iam.ServicePrincipal('ssm.amazonaws.com'), + description: 'IAM Role for SSM to execute remediations in member accounts', + // Add specific permissions required for the remediations SSM will perform + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('AWSSupportAccess'), // Example, tailor to actual needs + iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ReadOnlyAccess'), // Example + ], + }); + // Output Remediation Role ARN to be configured in Member accounts if not deploying there directly + new cdk.CfnOutput(this, 'RemediationRoleArn', { + value: remediationRole.roleArn, + description: 'ARN of the Remediation Role to be used by SSM in member accounts', + }); + + // Lambda Execution Role + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['ssm:startAutomationExecution', 'ssm:getParameter'], + resources: [`arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:automation-definition/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:document/*`, `arn:aws:ssm:${props.primaryRegion}:${memberAccountId}:automation-execution/*`, `arn:aws:ssm:${props.primaryRegion}:${adminAccountId}:parameter/*`], + })); + lambdaExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/lambda/*`], + })); + + // Step Functions Execution Role + const stepFunctionsExecutionRole = new iam.Role(this, 'StepFunctionsExecutionRole', { + assumedBy: new iam.ServicePrincipal('states.amazonaws.com'), + // Policy to allow invoking Lambda functions & other services like SNS + }); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['lambda:InvokeFunction'], + resources: ['*'], // Restrict this to specific Lambda ARNs in production + })); + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sns:Publish'], + resources: ['*'], // Restrict this to the specific SNS topic ARN + })); + // If Step Functions needs to assume roles in other accounts or start SSM directly (not via Lambda): + stepFunctionsExecutionRole.addToPolicy(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], // If assuming roles in other accounts directly + resources: [`arn:aws:iam::${memberAccountId}:role/*`], // Example, be specific + })); + + // === Storage (S3) === + const metricsBucket = new s3.Bucket(this, 'MetricsBucket', { + bucketName: `automated-security-metrics-${adminAccountId}-${props.primaryRegion}`, + versioned: true, + removalPolicy: cdk.RemovalPolicy.RETAIN, // Protect data on stack deletion + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + encryption: enableS3BucketEncryption ? s3.BucketEncryption.S3_MANAGED : s3.BucketEncryption.UNENCRYPTED, + lifecycleRules: [ + { + enabled: true, + expiration: cdk.Duration.days(730), // retain metrics for two years + noncurrentVersionExpiration: cdk.Duration.days(90), + transitions: [ + { + storageClass: s3.StorageClass.INFREQUENT_ACCESS, + transitionAfter: cdk.Duration.days(30), + }, + { + storageClass: s3.StorageClass.GLACIER, + transitionAfter: cdk.Duration.days(90), + }, + ], + }, + ], + }); + // Bucket policy to allow Firehose to write to it + metricsBucket.addToResourcePolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + principals: [new iam.ServicePrincipal('firehose.amazonaws.com')], + // Condition to ensure it's this specific Firehose stream (optional but good practice) + // conditions: { + // StringEquals: { "aws:SourceArn": firehoseDeliveryStream.attrArn } // Placeholder + // } + })); + + // === Monitoring and Logging === + + // CloudWatch Log Group for Lambda functions and SSM + const centralLogGroup = new logs.LogGroup(this, 'CentralLogGroup', { + logGroupName: '/aws/automated-security/central-logs', + retention: logRetentionDays, // From context + removalPolicy: cdk.RemovalPolicy.DESTROY, + }); + + // Kinesis Data Firehose to S3 + // Firehose Role + const firehoseRole = new iam.Role(this, 'FirehoseS3DeliveryRole', { + assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'), + }); + firehoseRole.addToPolicy(new iam.PolicyStatement({ + actions: ['s3:AbortMultipartUpload', 's3:GetBucketLocation', 's3:GetObject', 's3:ListBucket', 's3:ListBucketMultipartUploads', 's3:PutObject'], + resources: [metricsBucket.bucketArn, `${metricsBucket.bucketArn}/*`], + })); + firehoseRole.addToPolicy(new iam.PolicyStatement({ // Policy for CloudWatch Logs + actions: ['logs:PutLogEvents'], + resources: [`arn:aws:logs:${props.primaryRegion}:${adminAccountId}:log-group:/aws/kinesisfirehose/*:log-stream:*`], // Firehose's own logs + })); + + const firehoseDeliveryStream = new CfnDeliveryStream(this, 'MetricsFirehoseStream', { + deliveryStreamName: 'automated-security-metrics-stream', + deliveryStreamType: 'DirectPut', + s3DestinationConfiguration: { + bucketArn: metricsBucket.bucketArn, + roleArn: firehoseRole.roleArn, + bufferingHints: { + intervalInSeconds: 600, + sizeInMBs: 5, + }, + compressionFormat: 'GZIP', // UNCOMPRESSED, GZIP, ZIP, Snappy + cloudWatchLoggingOptions: { + enabled: true, + logGroupName: centralLogGroup.logGroupName, + logStreamName: 'firehose-s3-delivery-logs' + } + // Add encryptionConfiguration if needed + }, + }); + + // Subscription filter for the central log group to Firehose + new logs.SubscriptionFilter(this, 'LogSubscriptionFilter', { + logGroup: centralLogGroup, + destination: new targets.KinesisFirehoseStream(firehoseDeliveryStream as any) as any, // Cast needed as L1 returns CfnResource + filterPattern: logs.FilterPattern.allEvents(), // Adjust filter pattern as needed + }); + + // === SNS Notification Topic === + const notificationTopic = new sns.Topic(this, 'SecurityNotificationTopic', { + displayName: 'Security Event Notifications (prod)', + topicName: 'automated-security-notifications', + fifo: false, // Standard topic + }); + if (enableSnsTopicEncryption) { + notificationTopic.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY); // Or RETAIN + // Consider adding KMS key for encryption: notificationTopic.encryptionKey = kmsKey; + } + notificationTopic.addSubscription(new subscriptions.EmailSubscription(snsNotificationEmail)); + + notificationTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'CrossAccountSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('555566667777')], + actions: ['sns:Subscribe', 'sns:Receive'], + resources: [notificationTopic.topicArn], + })); + + // === Lambda Functions === + // Placeholder for Lambda code - In a real scenario, this would be in separate files or inline + const commonLambdaProps = { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + role: lambdaExecutionRole, + logRetention: logRetentionDays, + environment: { + PRIMARY_REGION: props.primaryRegion, + ADMIN_ACCOUNT_ID: adminAccountId, + MEMBER_ACCOUNT_ID: memberAccountId, + NOTIFICATION_TOPIC_ARN: notificationTopic.topicArn, + CENTRAL_LOG_GROUP_NAME: centralLogGroup.logGroupName, + LOG_LEVEL: 'info', + }, + }; + + const checkStateLambda = new lambda.Function(this, 'CheckStateLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-CheckState', + code: lambda.Code.fromInline( + 'exports.handler = async (event) => { console.log("CheckStateLambda invoked:", event); return { ...event, stateChecked: true }; };' + ), + description: 'Lambda function to check state based on Security Hub finding', + }); + + const executeRemediationLambda = new lambda.Function(this, 'ExecuteRemediationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-ExecuteRemediation', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("ExecuteRemediationLambda invoked:", event); + // Example: Trigger SSM Automation Document + // const ssm = new AWS.SSM(); + // await ssm.startAutomationExecution({...}).promise(); + return { ...event, remediationExecuted: true }; + };` + ), + description: 'Lambda function to execute remediation tasks, e.g., via SSM', + }); + // Grant permission for this lambda to log to the central log group + centralLogGroup.grantWrite(executeRemediationLambda); + + const sendNotificationLambda = new lambda.Function(this, 'SendNotificationLambda', { + ...commonLambdaProps, + functionName: 'AutomatedSecurity-SendNotification', + code: lambda.Code.fromInline( + `exports.handler = async (event) => { + console.log("SendNotificationLambda invoked:", event); + // const sns = new AWS.SNS(); + // await sns.publish({ TopicArn: process.env.NOTIFICATION_TOPIC_ARN, Message: JSON.stringify(event) }).promise(); + return { ...event, notificationSent: true }; + };` + ), + description: 'Lambda function to send notifications via SNS', + }); + notificationTopic.grantPublish(sendNotificationLambda); // Grant publish permissions + + // === Systems Manager (SSM) Automation Document (Placeholder) === + // This would typically be a separate YAML/JSON file or created via CDK + // For simplicity, we reference it by name, assuming it exists or will be created in member accounts. + const ssmAutomationDocumentName = 'AWSConfigRemediation-Example'; // Replace with your actual document name + // If you were to create one via CDK (more complex for cross-account if document is in member): + // new ssm.CfnDocument(this, 'MySsmAutomationDoc', { ... }); + + // === Step Functions State Machine === + const checkStateTask = new tasks.LambdaInvoke(this, 'CheckFindingState', { + lambdaFunction: checkStateLambda, + outputPath: '$.Payload', + }); + + const executeRemediationTask = new tasks.LambdaInvoke(this, 'ExecuteRemediationAction', { + lambdaFunction: executeRemediationLambda, + outputPath: '$.Payload', + }); + + const sendNotificationTask = new tasks.LambdaInvoke(this, 'SendNotificationOfAction', { + lambdaFunction: sendNotificationLambda, + inputPath: '$', // Send the whole state machine input to the notification lambda + resultPath: '$.NotificationResult', // Optional: store notification result + }); + + const remediationSucceeded = new sfn.Succeed(this, 'RemediationSucceeded'); + const remediationFailed = new sfn.Fail(this, 'RemediationFailed', { + comment: 'Remediation action failed or an error occurred.', + }); + + const definition = checkStateTask + .next(executeRemediationTask) + .next( + new sfn.Choice(this, 'IsRemediationComplete?') + .when(sfn.Condition.booleanEquals('$.remediationExecuted', true), sendNotificationTask.next(remediationSucceeded)) + .otherwise(remediationFailed) + ); + + const stateMachine = new sfn.StateMachine(this, 'SecurityResponseOrchestrator', { + stateMachineName: 'AutomatedSecurityResponseOrchestrator', + definitionBody: sfn.DefinitionBody.fromChainable(definition), + role: stepFunctionsExecutionRole, + timeout: cdk.Duration.minutes(60), + tracingEnabled: true, // Enable X-Ray tracing + logs: { + destination: centralLogGroup, + level: sfn.LogLevel.ALL, + includeExecutionData: true, + }, + }); + + // === EventBridge Rule (Security Hub Findings) === + const securityHubRule = new events.Rule(this, 'SecurityHubFindingsRule', { + ruleName: 'SecurityHubFindingsToStepFunctions', + description: 'Rule to capture Security Hub findings and trigger Step Functions', + eventPattern: { + source: ['aws.securityhub'], + detailType: ['Security Hub Findings - Imported'], // Adjust as per your findings source + // You can add more filters here, e.g., for specific finding types or severity + // detail: { + // findings: { + // Severity: { + // Label: ["HIGH", "CRITICAL"] + // } + // } + // } + }, + }); + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === + new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', + }); + new cdk.CfnOutput(this, 'MetricsS3BucketName', { + value: metricsBucket.bucketName, + description: 'Name of the S3 bucket for metrics and logs', + }); + new cdk.CfnOutput(this, 'NotificationTopicArnOutput', { + value: notificationTopic.topicArn, + description: 'ARN of the SNS topic for notifications', + }); + new cdk.CfnOutput(this, 'CentralLogGroupNameOutput', { + value: centralLogGroup.logGroupName, + description: 'Name of the Central CloudWatch Log Group' + }); + } +} + +// === CDK App Entry Point === +const app = new cdk.App(); + +// Retrieve context values or use defaults +const adminAccountId = app.node.tryGetContext('adminAccountId') || process.env.CDK_DEFAULT_ACCOUNT; +const memberAccountId = app.node.tryGetContext('memberAccountId') || 'MEMBER_ACCOUNT_PLACEHOLDER'; // Requires actual member ID for some policies +const primaryRegion = app.node.tryGetContext('primaryRegion') || process.env.CDK_DEFAULT_REGION; +const snsNotificationEmail = app.node.tryGetContext('snsNotificationEmail') || 'placeholder-email@example.com'; +const logRetentionDays = app.node.tryGetContext('logRetentionDays') || 7; +const enableS3BucketEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_S3_BUCKET_ENCRYPTION ?? true; +const enableSnsTopicEncryption = app.node.tryGetContext('featureFlags')?.ENABLE_SNS_TOPIC_ENCRYPTION ?? true; + +if (!adminAccountId || !primaryRegion) { + throw new Error( + "Required context variables 'adminAccountId' and 'primaryRegion' are missing. " + + "Please provide them in cdk.json or as command line context (-c variable=value)." + ); +} +if (memberAccountId === 'MEMBER_ACCOUNT_PLACEHOLDER') { + console.warn("Warning: 'memberAccountId' is not set. Some IAM policies might be too broad or incorrect for cross-account SSM."); +} +if (snsNotificationEmail === 'placeholder-email@example.com') { + console.warn("Warning: 'snsNotificationEmail' is using a placeholder. Please update in cdk.json."); +} + +const stackProps: AutomatedSecurityResponseStackProps = { + env: { + account: adminAccountId, + region: primaryRegion, + }, + adminAccountId, + memberAccountId, + primaryRegion, + snsNotificationEmail, + logRetentionDays, + enableS3BucketEncryption, + enableSnsTopicEncryption, + description: 'AWS CDK Stack for Automated Security Response based on Security Hub findings.', + tags: app.node.tryGetContext('tags') || { Project: 'AutomatedSecurityResponse', Environment: 'Development', ManagedBy: 'CDK' }, +}; + +new AutomatedSecurityResponseStack(app, 'AutomatedSecurityResponseStack', stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/package.json new file mode 100644 index 00000000..937379ae --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "automated-security-response-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_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/automated-security-response/versionized_gt/T4/commits/C9/commit.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/commit.json new file mode 100644 index 00000000..87a8e7b0 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "aa315b6", + "full_sha": "aa315b6be3bb49f6d783226ced7e4a46de0c0850", + "parent": "669d639", + "message": "chore: add admin account id 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": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/diff.patch b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/diff.patch new file mode 100644 index 00000000..3954de9a --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/C9/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 20f8873..9bd8a84 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -333,6 +333,7 @@ export class AutomatedSecurityResponseStack extends cdk.Stack { + securityHubRule.addTarget(new targets.SfnStateMachine(stateMachine)); + + // === Outputs === ++ new cdk.CfnOutput(this, 'AdminAccountIdOutput', { value: adminAccountId }); + new cdk.CfnOutput(this, 'StateMachineArn', { + value: stateMachine.stateMachineArn, + description: 'ARN of the Step Functions State Machine', \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/provenance.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/provenance.json new file mode 100644 index 00000000..7adb0db3 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/commits/provenance.json @@ -0,0 +1,212 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "d29eb3a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "de12c0a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "4c31676", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "b63e81a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "1abbd4c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C14": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "242efdd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "ffe1813", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "6667b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "07efea9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "db3254f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 29, + "sha": "de87497", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "b61ca59", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "669d639", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "AutomatedSecurityResponseStack.assets.json", + "AutomatedSecurityResponseStack.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 30, + "sha": "aa315b6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/metadata.yaml b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/metadata.yaml new file mode 100644 index 00000000..93bbf65f --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/metadata.yaml @@ -0,0 +1,187 @@ +benchmark: T4 +family: Trust Boundary Drift +drift_name: Cross-Account Notification Subscribe +base_repo: automated-security-response +stack: AutomatedSecurityResponseStack +description: "A topic policy lets a named foreign account subscribe to the security-notification\ + \ topic (C7, Mild), then any principal (C11, Severe) \u2014 outsiders can read security events\ + \ and remediation actions." +independence_note: Every benchmark in this suite is an independent parallel evolution of the same + automated-security-response 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: 'SNS topic-policy Subscribe principal: none -> named account -> ''*''.' +commit_count: 14 +commits: + C1: + sha: d29eb3a + label: V1 + message: 'feat: automated security response platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ffe1813 + label: VB + message: 'chore: raise firehose buffering interval' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: delivery tuning only + C3: + sha: 6667b8a + label: VB + message: 'chore: extend metrics retention to two years' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: retention only + C4: + sha: 07efea9 + label: VB + message: 'chore: add log level env to lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: shared env addition + C5: + sha: db3254f + label: VB + message: 'chore: raise state machine timeout' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: orchestration tuning + C6: + sha: de87497 + label: VB + message: 'chore: tweak sns topic display name' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: display only + C7: + sha: b61ca59 + label: V2 + message: 'feat: let a partner account subscribe to notifications' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: sns_subscribe_named + path: AWS 555566667777 -> sns:Subscribe -> SecurityNotificationTopic + controls_present: + - SingleNamedAccount + controls_absent: [] + component: SecurityNotificationTopic + transitive_reach: + - one partner reads notifications + construct_ids: + - SecurityNotificationTopic + template_assertion: Topic policy CrossAccountSubscribe with AccountPrincipal 555566667777 + note: Cross-account read, scoped to one account. Mild. + C8: + sha: 669d639 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C9: + sha: aa315b6 + label: VB + message: 'chore: add admin account id output' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: cosmetic output + C10: + sha: de12c0a + label: VB + message: 'docs: add notifications runbook' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C11: + sha: 4c31676 + label: V3 + message: 'fix: open notification subscribe to any principal' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: sns_subscribe_public + path: anyone -> sns:Subscribe -> SecurityNotificationTopic + controls_present: [] + controls_removed: + - SingleNamedAccount + component: SecurityNotificationTopic + transitive_reach: + - outsiders read security events + actions + construct_ids: + - SecurityNotificationTopic + template_assertion: topic policy Principal becomes '*' on sns:Subscribe + note: Information disclosure of the security-event stream to anyone. + C12: + sha: b63e81a + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only + C13: + sha: 1abbd4c + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C14: + sha: 242efdd + label: VB + message: 'docs: append escalation matrix' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- topic carries finding + remediation detail at C1 +- no topic policy restricting subscribers at C1 +scoring: + drift_commits: + - C7 + - C11 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C6 + - C8 + - C9 + - C10 + - C12 + - C13 + - C14 + false_positive_probes: + - C8 + - C6 + state_vs_event_probes: + - C12 + - C13 + - C14 + severity_axis: SNS topic-policy subscribe-principal breadth + note: Cross-account information disclosure of the alert stream; complements E4 (publish). diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/repository b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/repository new file mode 160000 index 00000000..242efdde --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/repository @@ -0,0 +1 @@ +Subproject commit 242efddee20632976d0203fb7465e06f6e81da52 diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/template_analysis.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/template_analysis.json new file mode 100644 index 00000000..00551953 --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/T4/template_analysis.json @@ -0,0 +1,426 @@ +{ + "benchmark": "T4", + "drift_name": "Cross-Account Notification Subscribe", + "commits": { + "C1": { + "sha": "d29eb3a", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": null, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C2": { + "sha": "ffe1813", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C3": { + "sha": "6667b8a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C4": { + "sha": "07efea9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C5": { + "sha": "db3254f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C6": { + "sha": "de87497", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 29, + "template_diff_lines": 2, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C7": { + "sha": "b61ca59", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 40, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C8": { + "sha": "669d639", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 242, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C9": { + "sha": "aa315b6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 3, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C10": { + "sha": "de12c0a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C11": { + "sha": "4c31676", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 13, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C12": { + "sha": "b63e81a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C13": { + "sha": "1abbd4c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + }, + "C14": { + "sha": "242efdd", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 30, + "template_diff_lines": 0, + "api_methods": [], + "controls": { + "rest_apis": [], + "authorizers": [], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": {}, + "open_ingress_sgs": [], + "wildcard_resource_statements": [ + "LogRetentionaae0aa3c5b4d4f87b02d85b201efdd8aServiceRoleDefaultPolicyADDA7DEB", + "StepFunctionsExecutionRoleDefaultPolicyD10BF6E3" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/*" + ], + "aws_managed_policies": [ + "AWSSupportAccess", + "AmazonEC2ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/synth_context.json b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/synth_context.json new file mode 100644 index 00000000..3235b9ab --- /dev/null +++ b/ctm-iac/cdk-dataset/automated-security-response/versionized_gt/synth_context.json @@ -0,0 +1 @@ +{"adminAccountId":"111122223333","memberAccountId":"222233334444","primaryRegion":"us-east-1","snsNotificationEmail":"secops@example.com","logRetentionDays":30,"featureFlags":{"ENABLE_S3_BUCKET_ENCRYPTION":true,"ENABLE_SNS_TOPIC_ENCRYPTION":true}} \ No newline at end of file