@prism-d1/cli 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/assets/github-workflows/prism-eval-gate-kiro.yml +2 -2
  2. package/dist/assets/infra/bin/app.ts +61 -37
  3. package/dist/assets/infra/lib/constructs/security-agent-construct.ts +35 -30
  4. package/dist/assets/infra/lib/metrics-pipeline-stack.ts +2 -110
  5. package/dist/assets/infra/lib/security-agent-resources.ts +136 -0
  6. package/dist/assets/infra/lib/security-agent-stack.ts +69 -0
  7. package/dist/assets/pentest-cdk/bin/app.ts +12 -0
  8. package/dist/assets/pentest-cdk/cdk.json +7 -0
  9. package/dist/assets/pentest-cdk/lib/pentest-target-stack.ts +206 -0
  10. package/dist/assets/pentest-cdk/package.json +18 -0
  11. package/dist/assets/pentest-cdk/tsconfig.json +12 -0
  12. package/dist/src/codewatch/results.d.ts +124 -4
  13. package/dist/src/codewatch/results.d.ts.map +1 -1
  14. package/dist/src/codewatch/results.js +8 -2
  15. package/dist/src/codewatch/results.js.map +1 -1
  16. package/dist/src/commands/_top/version.d.ts +62 -0
  17. package/dist/src/commands/_top/version.d.ts.map +1 -0
  18. package/dist/src/commands/_top/version.js +175 -0
  19. package/dist/src/commands/_top/version.js.map +1 -0
  20. package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts +12 -6
  21. package/dist/src/commands/bootstrapper/setup-otel-sync.d.ts.map +1 -1
  22. package/dist/src/commands/bootstrapper/setup-otel-sync.js +18 -409
  23. package/dist/src/commands/bootstrapper/setup-otel-sync.js.map +1 -1
  24. package/dist/src/commands/enable/codeburn.d.ts +59 -0
  25. package/dist/src/commands/enable/codeburn.d.ts.map +1 -0
  26. package/dist/src/commands/enable/codeburn.js +531 -0
  27. package/dist/src/commands/enable/codeburn.js.map +1 -0
  28. package/dist/src/commands/enable/security.d.ts +41 -0
  29. package/dist/src/commands/enable/security.d.ts.map +1 -0
  30. package/dist/src/commands/enable/security.js +130 -0
  31. package/dist/src/commands/enable/security.js.map +1 -0
  32. package/dist/src/commands/export/otel.d.ts +31 -0
  33. package/dist/src/commands/export/otel.d.ts.map +1 -0
  34. package/dist/src/commands/export/otel.js +52 -0
  35. package/dist/src/commands/export/otel.js.map +1 -0
  36. package/dist/src/commands/security/code-review.d.ts +22 -0
  37. package/dist/src/commands/security/code-review.d.ts.map +1 -0
  38. package/dist/src/commands/security/code-review.js +43 -0
  39. package/dist/src/commands/security/code-review.js.map +1 -0
  40. package/dist/src/commands/security/findings.d.ts +15 -0
  41. package/dist/src/commands/security/findings.d.ts.map +1 -0
  42. package/dist/src/commands/security/findings.js +31 -0
  43. package/dist/src/commands/security/findings.js.map +1 -0
  44. package/dist/src/commands/security/pentest.d.ts +18 -0
  45. package/dist/src/commands/security/pentest.d.ts.map +1 -0
  46. package/dist/src/commands/security/pentest.js +32 -0
  47. package/dist/src/commands/security/pentest.js.map +1 -0
  48. package/dist/src/commands/security/threat-model.d.ts +16 -0
  49. package/dist/src/commands/security/threat-model.d.ts.map +1 -0
  50. package/dist/src/commands/security/threat-model.js +24 -0
  51. package/dist/src/commands/security/threat-model.js.map +1 -0
  52. package/dist/src/commands/security/threats.d.ts +12 -0
  53. package/dist/src/commands/security/threats.d.ts.map +1 -0
  54. package/dist/src/commands/security/threats.js +23 -0
  55. package/dist/src/commands/security/threats.js.map +1 -0
  56. package/dist/src/commands/securityagent/setup.d.ts +19 -5
  57. package/dist/src/commands/securityagent/setup.d.ts.map +1 -1
  58. package/dist/src/commands/securityagent/setup.js +25 -189
  59. package/dist/src/commands/securityagent/setup.js.map +1 -1
  60. package/dist/src/dashboard/codewatch.d.ts.map +1 -1
  61. package/dist/src/dashboard/codewatch.js +2 -1
  62. package/dist/src/dashboard/codewatch.js.map +1 -1
  63. package/dist/src/dashboard/page.d.ts.map +1 -1
  64. package/dist/src/dashboard/page.js +159 -4
  65. package/dist/src/dashboard/page.js.map +1 -1
  66. package/dist/src/dashboard/payload.d.ts +8 -5
  67. package/dist/src/dashboard/payload.d.ts.map +1 -1
  68. package/dist/src/dashboard/payload.js +5 -2
  69. package/dist/src/dashboard/payload.js.map +1 -1
  70. package/dist/src/dashboard/security.d.ts +130 -0
  71. package/dist/src/dashboard/security.d.ts.map +1 -0
  72. package/dist/src/dashboard/security.js +144 -0
  73. package/dist/src/dashboard/security.js.map +1 -0
  74. package/dist/src/dashboard/setup.d.ts +15 -0
  75. package/dist/src/dashboard/setup.d.ts.map +1 -1
  76. package/dist/src/dashboard/setup.js +20 -3
  77. package/dist/src/dashboard/setup.js.map +1 -1
  78. package/dist/src/dashboard/usage.js +1 -1
  79. package/dist/src/dashboard/usage.js.map +1 -1
  80. package/dist/src/index.d.ts.map +1 -1
  81. package/dist/src/index.js +9 -2
  82. package/dist/src/index.js.map +1 -1
  83. package/dist/src/security/cfn-diff.d.ts +57 -0
  84. package/dist/src/security/cfn-diff.d.ts.map +1 -0
  85. package/dist/src/security/cfn-diff.js +112 -0
  86. package/dist/src/security/cfn-diff.js.map +1 -0
  87. package/dist/src/security/errors.d.ts +45 -0
  88. package/dist/src/security/errors.d.ts.map +1 -0
  89. package/dist/src/security/errors.js +116 -0
  90. package/dist/src/security/errors.js.map +1 -0
  91. package/dist/src/security/marker.d.ts +46 -0
  92. package/dist/src/security/marker.d.ts.map +1 -0
  93. package/dist/src/security/marker.js +120 -0
  94. package/dist/src/security/marker.js.map +1 -0
  95. package/dist/src/security/pentest-dockerfile.d.ts +8 -0
  96. package/dist/src/security/pentest-dockerfile.d.ts.map +1 -0
  97. package/dist/src/security/pentest-dockerfile.js +163 -0
  98. package/dist/src/security/pentest-dockerfile.js.map +1 -0
  99. package/dist/src/security/pentest.d.ts +233 -0
  100. package/dist/src/security/pentest.d.ts.map +1 -0
  101. package/dist/src/security/pentest.js +1057 -0
  102. package/dist/src/security/pentest.js.map +1 -0
  103. package/dist/src/security/repo.d.ts +25 -0
  104. package/dist/src/security/repo.d.ts.map +1 -0
  105. package/dist/src/security/repo.js +38 -0
  106. package/dist/src/security/repo.js.map +1 -0
  107. package/dist/src/security/review.d.ts +202 -0
  108. package/dist/src/security/review.d.ts.map +1 -0
  109. package/dist/src/security/review.js +706 -0
  110. package/dist/src/security/review.js.map +1 -0
  111. package/dist/src/security/setup.d.ts +193 -0
  112. package/dist/src/security/setup.d.ts.map +1 -0
  113. package/dist/src/security/setup.js +728 -0
  114. package/dist/src/security/setup.js.map +1 -0
  115. package/dist/src/security/threat-model.d.ts +133 -0
  116. package/dist/src/security/threat-model.d.ts.map +1 -0
  117. package/dist/src/security/threat-model.js +653 -0
  118. package/dist/src/security/threat-model.js.map +1 -0
  119. package/dist/src/utils/exec.d.ts +6 -0
  120. package/dist/src/utils/exec.d.ts.map +1 -1
  121. package/dist/src/utils/exec.js +12 -1
  122. package/dist/src/utils/exec.js.map +1 -1
  123. package/dist/src/utils/otel-sync-schedule.d.ts +19 -0
  124. package/dist/src/utils/otel-sync-schedule.d.ts.map +1 -1
  125. package/dist/src/utils/otel-sync-schedule.js +24 -2
  126. package/dist/src/utils/otel-sync-schedule.js.map +1 -1
  127. package/package.json +3 -3
@@ -238,7 +238,7 @@ jobs:
238
238
  fi
239
239
 
240
240
  # Shared rule set: .prism/review/rules.md is the one source for this
241
- # gate and the local pre-push review (prism-cli enable security).
241
+ # gate and the local pre-push review (prism-cli enable codewatch).
242
242
  # kiro-cli loads rules from .kiro/steering/, so sync it in for this run.
243
243
  if [ -f .prism/review/rules.md ]; then
244
244
  mkdir -p .kiro/steering
@@ -648,7 +648,7 @@ jobs:
648
648
 
649
649
  if [ -z "$CODE_REVIEW_ID" ]; then
650
650
  echo "::error::Could not create the Continuum Code Review resource: ${CR_OUTPUT}"
651
- echo " Run 'prism-cli securityagent setup' to create it manually."
651
+ echo " Run 'prism-cli enable security' to create it manually."
652
652
  echo "scan_error=true" >> "$GITHUB_OUTPUT"
653
653
  echo "findings=-1" >> "$GITHUB_OUTPUT"
654
654
  exit 0
@@ -6,6 +6,7 @@ import { AwsSolutionsChecks } from 'cdk-nag';
6
6
  import { MetricsPipelineStack } from '../lib/metrics-pipeline-stack';
7
7
  import { ApiStack } from '../lib/api-stack';
8
8
  import { DashboardStack } from '../lib/dashboard-stack';
9
+ import { SecurityAgentStack } from '../lib/security-agent-stack';
9
10
 
10
11
  const app = new cdk.App();
11
12
 
@@ -14,43 +15,66 @@ const env: cdk.Environment = {
14
15
  region: process.env.CDK_DEFAULT_REGION ?? 'us-west-2',
15
16
  };
16
17
 
17
- const pipelineStack = new MetricsPipelineStack(app, 'PrismD1MetricsPipeline', {
18
- env,
19
- description: 'PRISM D1 Velocity - Core metrics event pipeline (EventBridge, DynamoDB)',
20
- tags: {
21
- 'prism:project': 'PRISM',
22
- 'prism:domain': 'D1-Velocity',
23
- 'prism:component': 'MetricsPipeline',
24
- },
25
- });
26
-
27
- const apiStack = new ApiStack(app, 'PrismD1Api', {
28
- env,
29
- description: 'PRISM D1 Velocity - Metric ingestion and query API',
30
- eventBus: pipelineStack.eventBus,
31
- eventsTable: pipelineStack.eventsTable,
32
- metadataTable: pipelineStack.metadataTable,
33
- kmsKey: pipelineStack.kmsKey,
34
- tags: {
35
- 'prism:project': 'PRISM',
36
- 'prism:domain': 'D1-Velocity',
37
- 'prism:component': 'Api',
38
- },
39
- });
40
-
41
- const dashboardStack = new DashboardStack(app, 'PrismD1Dashboard', {
42
- env,
43
- description: 'PRISM D1 Velocity - CloudWatch dashboards and alarms',
44
- productivityWidgetArn: pipelineStack.otelCollector?.productivityWidgetArn,
45
- velocityWidgetArn: pipelineStack.velocityWidgetArn,
46
- tags: {
47
- 'prism:project': 'PRISM',
48
- 'prism:domain': 'D1-Velocity',
49
- 'prism:component': 'Dashboard',
50
- },
51
- });
52
-
53
- apiStack.addDependency(pipelineStack);
18
+ // securityAgentStandalone=true builds ONLY PrismD1SecurityAgent: AWS Security
19
+ // Agent without the metrics pipeline (what `prism-cli enable security` deploys
20
+ // in an account that does not host it in the pipeline). It and
21
+ // enableSecurityAgent=true (Security Agent inside PrismD1MetricsPipeline) use
22
+ // the same resource and export names, so one account can only have one.
23
+ const standalone = app.node.tryGetContext('securityAgentStandalone') === 'true';
24
+ if (standalone && app.node.tryGetContext('enableSecurityAgent') === 'true') {
25
+ throw new Error('securityAgentStandalone=true and enableSecurityAgent=true are exclusive: Security Agent lives either in PrismD1SecurityAgent or in PrismD1MetricsPipeline, never both.');
26
+ }
27
+
28
+ if (standalone) {
29
+ new SecurityAgentStack(app, 'PrismD1SecurityAgent', {
30
+ env,
31
+ description: 'PRISM D1 Velocity - AWS Security Agent (standalone, without the metrics pipeline)',
32
+ kmsKeyArn: app.node.tryGetContext('securityAgentKmsKeyArn') as string | undefined,
33
+ tags: {
34
+ 'prism:project': 'PRISM',
35
+ 'prism:domain': 'D1-Velocity',
36
+ 'prism:component': 'SecurityAgent',
37
+ },
38
+ });
39
+ } else {
40
+ const pipelineStack = new MetricsPipelineStack(app, 'PrismD1MetricsPipeline', {
41
+ env,
42
+ description: 'PRISM D1 Velocity - Core metrics event pipeline (EventBridge, DynamoDB)',
43
+ tags: {
44
+ 'prism:project': 'PRISM',
45
+ 'prism:domain': 'D1-Velocity',
46
+ 'prism:component': 'MetricsPipeline',
47
+ },
48
+ });
49
+
50
+ const apiStack = new ApiStack(app, 'PrismD1Api', {
51
+ env,
52
+ description: 'PRISM D1 Velocity - Metric ingestion and query API',
53
+ eventBus: pipelineStack.eventBus,
54
+ eventsTable: pipelineStack.eventsTable,
55
+ metadataTable: pipelineStack.metadataTable,
56
+ kmsKey: pipelineStack.kmsKey,
57
+ tags: {
58
+ 'prism:project': 'PRISM',
59
+ 'prism:domain': 'D1-Velocity',
60
+ 'prism:component': 'Api',
61
+ },
62
+ });
63
+
64
+ const dashboardStack = new DashboardStack(app, 'PrismD1Dashboard', {
65
+ env,
66
+ description: 'PRISM D1 Velocity - CloudWatch dashboards and alarms',
67
+ productivityWidgetArn: pipelineStack.otelCollector?.productivityWidgetArn,
68
+ velocityWidgetArn: pipelineStack.velocityWidgetArn,
69
+ tags: {
70
+ 'prism:project': 'PRISM',
71
+ 'prism:domain': 'D1-Velocity',
72
+ 'prism:component': 'Dashboard',
73
+ },
74
+ });
75
+
76
+ apiStack.addDependency(pipelineStack);
77
+ }
54
78
 
55
79
  // Enable cdk-nag AWS Solutions checks on all stacks
56
80
  Aspects.of(app).add(new AwsSolutionsChecks({ verbose: true }));
@@ -168,6 +168,32 @@ export class SecurityAgentConstruct extends Construct {
168
168
  );
169
169
  }
170
170
 
171
+ // -------------------------------------------------------
172
+ // S3 Scan Bucket — stores repo ZIPs and diff files for
173
+ // API-triggered code reviews (diff scans from CI/CD) and
174
+ // threat models. Created before the agent space, which lists it.
175
+ // -------------------------------------------------------
176
+ this.scanBucket = new s3.Bucket(this, 'ScanBucket', {
177
+ bucketName: `security-agent-scans-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`,
178
+ // SSE-S3 (not the customer-managed KMS key): the Continuum service reads
179
+ // this bucket when creating the code review and cannot access a CMK it
180
+ // has no grant on. Artifacts here are ephemeral diffs (30-day lifecycle),
181
+ // the bucket is private (BLOCK_ALL) and SSL-enforced.
182
+ encryption: s3.BucketEncryption.S3_MANAGED,
183
+ blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
184
+ enforceSSL: true,
185
+ versioned: false,
186
+ lifecycleRules: [
187
+ {
188
+ id: 'expire-scan-artifacts',
189
+ expiration: cdk.Duration.days(30),
190
+ enabled: true,
191
+ },
192
+ ],
193
+ removalPolicy: cdk.RemovalPolicy.DESTROY,
194
+ autoDeleteObjects: true,
195
+ });
196
+
171
197
  // Agent Space
172
198
  const agentSpace = new securityagent.CfnAgentSpace(this, 'AgentSpace', {
173
199
  name: props.agentSpaceName,
@@ -177,6 +203,9 @@ export class SecurityAgentConstruct extends Construct {
177
203
  }),
178
204
  awsResources: {
179
205
  iamRoles: [this.serviceRole.roleArn],
206
+ // create-threat-model refuses S3 sources and scope docs in a bucket the
207
+ // agent space does not list ("S3 bucket not found in agent instance S3 buckets").
208
+ s3Buckets: [this.scanBucket.bucketArn],
180
209
  },
181
210
  tags: [
182
211
  { key: 'prism:component', value: 'security-agent' },
@@ -210,7 +239,7 @@ export class SecurityAgentConstruct extends Construct {
210
239
 
211
240
  // Log group for pen test results (Security Agent writes to /aws/securityagent/<space>/pt-<id>)
212
241
  // Note: we do NOT set logGroupName to avoid conflicts with pre-existing log groups
213
- // created by prism-cli securityagent setup or the Security Agent service itself.
242
+ // created by prism-cli enable security or the Security Agent service itself.
214
243
  const pentestLogGroup = logs.LogGroup.fromLogGroupName(
215
244
  this, 'PentestLogGroup',
216
245
  `/aws/securityagent/${props.agentSpaceName}`,
@@ -237,31 +266,6 @@ export class SecurityAgentConstruct extends Construct {
237
266
  });
238
267
  }
239
268
 
240
- // -------------------------------------------------------
241
- // S3 Scan Bucket — stores repo ZIPs and diff files for
242
- // API-triggered code reviews (diff scans from CI/CD).
243
- // -------------------------------------------------------
244
- this.scanBucket = new s3.Bucket(this, 'ScanBucket', {
245
- bucketName: `security-agent-scans-${cdk.Aws.ACCOUNT_ID}-${cdk.Aws.REGION}`,
246
- // SSE-S3 (not the customer-managed KMS key): the Continuum service reads
247
- // this bucket when creating the code review and cannot access a CMK it
248
- // has no grant on. Artifacts here are ephemeral diffs (30-day lifecycle),
249
- // the bucket is private (BLOCK_ALL) and SSL-enforced.
250
- encryption: s3.BucketEncryption.S3_MANAGED,
251
- blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
252
- enforceSSL: true,
253
- versioned: false,
254
- lifecycleRules: [
255
- {
256
- id: 'expire-scan-artifacts',
257
- expiration: cdk.Duration.days(30),
258
- enabled: true,
259
- },
260
- ],
261
- removalPolicy: cdk.RemovalPolicy.DESTROY,
262
- autoDeleteObjects: true,
263
- });
264
-
265
269
  // Grant Security Agent service role read access to the scan bucket
266
270
  this.scanBucket.grantRead(this.serviceRole);
267
271
 
@@ -298,10 +302,11 @@ export class SecurityAgentConstruct extends Construct {
298
302
  // Code Review resource — NOT created here in CDK.
299
303
  // CreateCodeReview requires assets.sourceCode pointing to
300
304
  // an existing ZIP of the repo (chicken-and-egg at deploy).
301
- // Instead, `prism-cli securityagent setup` handles:
302
- // 1. git archive → upload repo.zip to scan bucket
305
+ // Instead, `prism-cli enable security` (or the CI eval gate on its
306
+ // first run) handles, per repo:
307
+ // 1. git archive → upload repo/<repo-slug>/latest.zip to the scan bucket
303
308
  // 2. CreateCodeReview API call
304
- // 3. Write code-review-id to SSM
309
+ // 3. Write /prism/continuum/code-review-id/<repo-slug> to SSM
305
310
  // The CDK only provisions the bucket + role + SSM scaffold.
306
311
  // -------------------------------------------------------
307
312
 
@@ -325,7 +330,7 @@ export class SecurityAgentConstruct extends Construct {
325
330
  tier: ssm.ParameterTier.STANDARD,
326
331
  });
327
332
 
328
- // code-review-id is written by `prism-cli securityagent setup` (not CDK)
333
+ // code-review-id/<repo-slug> is written by `prism-cli enable security` (not CDK)
329
334
  // because CreateCodeReview needs an existing repo ZIP in S3 first.
330
335
 
331
336
  new ssm.StringParameter(this, 'ParamScanBucket', {
@@ -15,6 +15,7 @@ import * as ec2 from 'aws-cdk-lib/aws-ec2';
15
15
  import { PrismVpcConstruct } from './constructs/prism-vpc-construct';
16
16
  import { GuardrailEnforcerConstruct } from './constructs/guardrail-enforcer-construct';
17
17
  import { SecurityAgentConstruct } from './constructs/security-agent-construct';
18
+ import { addSecurityAgentResources } from './security-agent-resources';
18
19
  import { OtelCollectorConstruct } from './constructs/otel-collector-construct';
19
20
  import { NagSuppressions } from 'cdk-nag';
20
21
 
@@ -613,116 +614,7 @@ export class MetricsPipelineStack extends cdk.Stack {
613
614
  // -------------------------------------------------------
614
615
  const enableSecurityAgent = this.node.tryGetContext('enableSecurityAgent') === 'true';
615
616
  if (enableSecurityAgent) {
616
- this.securityAgent = new SecurityAgentConstruct(this, 'SecurityAgent', {
617
- agentSpaceName: 'prism-d1-security',
618
- description: 'PRISM D1 Continuum space for design review, code review, and pen testing',
619
- kmsKey: prismKmsKey,
620
- codeRemediationStrategy: 'DISABLED',
621
- tags: {
622
- 'prism:pillar': 'security',
623
- },
624
- });
625
-
626
- // Grant GitHub OIDC roles access to scan bucket and Continuum APIs.
627
- // The OIDC role is created by `prism-cli bootstrapper setup-github-oidc`
628
- // with name pattern prism-d1-github-oidc-*.
629
- this.securityAgent.scanBucket.addToResourcePolicy(
630
- new iam.PolicyStatement({
631
- effect: iam.Effect.ALLOW,
632
- principals: [new iam.ArnPrincipal(`arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:root`)],
633
- actions: ['s3:PutObject', 's3:GetObject'],
634
- resources: [this.securityAgent.scanBucket.arnForObjects('*')],
635
- conditions: {
636
- ArnLike: {
637
- 'aws:PrincipalArn': `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/prism-d1-github-oidc-*`,
638
- },
639
- },
640
- }),
641
- );
642
-
643
- // Create a managed policy for Continuum scan API access.
644
- // Attached to the OIDC role by the bootstrapper setup script.
645
- const continuumCiPolicy = new iam.ManagedPolicy(this, 'ContinuumCiPolicy', {
646
- managedPolicyName: 'prism-d1-continuum-ci-scan',
647
- description: 'Grants GitHub Actions workflows access to Continuum diff scan APIs and SSM config',
648
- statements: [
649
- new iam.PolicyStatement({
650
- sid: 'ContinuumScanAPIs',
651
- effect: iam.Effect.ALLOW,
652
- actions: [
653
- 'securityagent:StartCodeReviewJob',
654
- 'securityagent:BatchGetCodeReviewJobs',
655
- 'securityagent:BatchGetCodeReviewJobTasks',
656
- 'securityagent:ListFindings',
657
- 'securityagent:BatchGetFindings',
658
- 'securityagent:CreateCodeReview',
659
- 'securityagent:ListCodeReviews',
660
- ],
661
- resources: [
662
- `arn:aws:securityagent:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:agent-space/*`,
663
- ],
664
- }),
665
- new iam.PolicyStatement({
666
- sid: 'PassRoleForCodeReview',
667
- effect: iam.Effect.ALLOW,
668
- actions: ['iam:PassRole'],
669
- resources: [this.securityAgent.serviceRole.roleArn],
670
- conditions: {
671
- StringEquals: { 'iam:PassedToService': 'securityagent.amazonaws.com' },
672
- },
673
- }),
674
- new iam.PolicyStatement({
675
- sid: 'ScanBucketWrite',
676
- effect: iam.Effect.ALLOW,
677
- actions: ['s3:PutObject', 's3:GetObject', 's3:ListBucket'],
678
- resources: [
679
- this.securityAgent.scanBucket.bucketArn,
680
- this.securityAgent.scanBucket.arnForObjects('*'),
681
- ],
682
- }),
683
- new iam.PolicyStatement({
684
- sid: 'KMSForScanBucket',
685
- effect: iam.Effect.ALLOW,
686
- actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
687
- resources: [prismKmsKey.keyArn],
688
- }),
689
- new iam.PolicyStatement({
690
- sid: 'SSMReadConfig',
691
- effect: iam.Effect.ALLOW,
692
- actions: ['ssm:GetParameter', 'ssm:GetParameters', 'ssm:PutParameter'],
693
- resources: [
694
- `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/prism/continuum/*`,
695
- ],
696
- }),
697
- ],
698
- });
699
-
700
- NagSuppressions.addResourceSuppressions(
701
- continuumCiPolicy,
702
- [
703
- {
704
- id: 'AwsSolutions-IAM5',
705
- reason: 'Agent space ID is dynamic; wildcard scoped to securityagent service resources only',
706
- appliesTo: [`Resource::arn:aws:securityagent:<AWS::Region>:<AWS::AccountId>:agent-space/*`],
707
- },
708
- {
709
- id: 'AwsSolutions-IAM5',
710
- reason: 'Scan bucket objects include dynamic diff filenames (SHA-based); wildcard on objects only',
711
- appliesTo: ['Resource::<SecurityAgentScanBucketB752F4A9.Arn>/*'],
712
- },
713
- {
714
- id: 'AwsSolutions-IAM5',
715
- reason: 'SSM parameters under /prism/continuum/ are all non-sensitive config values (IDs and bucket names)',
716
- appliesTo: ['Resource::arn:aws:ssm:<AWS::Region>:<AWS::AccountId>:parameter/prism/continuum/*'],
717
- },
718
- ],
719
- true,
720
- );
721
-
722
- new cdk.CfnOutput(this, 'ContinuumCiPolicyArn', {
723
- value: continuumCiPolicy.managedPolicyArn,
724
- description: 'Attach this policy to your GitHub OIDC role for Continuum CI scans',
725
- });
617
+ this.securityAgent = addSecurityAgentResources(this, prismKmsKey);
726
618
  }
727
619
 
728
620
  // -------------------------------------------------------
@@ -0,0 +1,136 @@
1
+ /**
2
+ * The Security Agent resources and the CI access to them, shared by the two
3
+ * places they can live:
4
+ *
5
+ * - PrismD1MetricsPipeline, with context enableSecurityAgent=true (the
6
+ * original layout; existing accounts keep it);
7
+ * - PrismD1SecurityAgent, the standalone stack (context
8
+ * securityAgentStandalone=true), for accounts that want Security Agent
9
+ * without the metrics pipeline.
10
+ *
11
+ * Construct ids are relative to the given scope, so the pipeline's logical ids
12
+ * are unchanged by moving this code here. Both layouts use the same fixed names
13
+ * (role, managed policy, bucket, SSM parameters, exports), so CloudFormation
14
+ * refuses a second copy in the same account and region.
15
+ */
16
+ import * as cdk from 'aws-cdk-lib';
17
+ import * as iam from 'aws-cdk-lib/aws-iam';
18
+ import * as kms from 'aws-cdk-lib/aws-kms';
19
+ import { Construct } from 'constructs';
20
+ import { NagSuppressions } from 'cdk-nag';
21
+ import { SecurityAgentConstruct } from './constructs/security-agent-construct';
22
+
23
+ export function addSecurityAgentResources(scope: Construct, kmsKey: kms.IKey): SecurityAgentConstruct {
24
+ const securityAgent = new SecurityAgentConstruct(scope, 'SecurityAgent', {
25
+ agentSpaceName: 'prism-d1-security',
26
+ description: 'PRISM D1 Continuum space for design review, code review, and pen testing',
27
+ kmsKey: kmsKey,
28
+ codeRemediationStrategy: 'DISABLED',
29
+ tags: {
30
+ 'prism:pillar': 'security',
31
+ },
32
+ });
33
+
34
+ // Grant GitHub OIDC roles access to scan bucket and Continuum APIs.
35
+ // The OIDC role is created by `prism-cli bootstrapper setup-github-oidc`
36
+ // with name pattern prism-d1-github-oidc-*.
37
+ securityAgent.scanBucket.addToResourcePolicy(
38
+ new iam.PolicyStatement({
39
+ effect: iam.Effect.ALLOW,
40
+ principals: [new iam.ArnPrincipal(`arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:root`)],
41
+ actions: ['s3:PutObject', 's3:GetObject'],
42
+ resources: [securityAgent.scanBucket.arnForObjects('*')],
43
+ conditions: {
44
+ ArnLike: {
45
+ 'aws:PrincipalArn': `arn:aws:iam::${cdk.Aws.ACCOUNT_ID}:role/prism-d1-github-oidc-*`,
46
+ },
47
+ },
48
+ }),
49
+ );
50
+
51
+ // Create a managed policy for Continuum scan API access.
52
+ // Attached to the OIDC role by the bootstrapper setup script.
53
+ const continuumCiPolicy = new iam.ManagedPolicy(scope, 'ContinuumCiPolicy', {
54
+ managedPolicyName: 'prism-d1-continuum-ci-scan',
55
+ description: 'Grants GitHub Actions workflows access to Continuum diff scan APIs and SSM config',
56
+ statements: [
57
+ new iam.PolicyStatement({
58
+ sid: 'ContinuumScanAPIs',
59
+ effect: iam.Effect.ALLOW,
60
+ actions: [
61
+ 'securityagent:StartCodeReviewJob',
62
+ 'securityagent:BatchGetCodeReviewJobs',
63
+ 'securityagent:BatchGetCodeReviewJobTasks',
64
+ 'securityagent:ListFindings',
65
+ 'securityagent:BatchGetFindings',
66
+ 'securityagent:CreateCodeReview',
67
+ 'securityagent:ListCodeReviews',
68
+ ],
69
+ resources: [
70
+ `arn:aws:securityagent:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:agent-space/*`,
71
+ ],
72
+ }),
73
+ new iam.PolicyStatement({
74
+ sid: 'PassRoleForCodeReview',
75
+ effect: iam.Effect.ALLOW,
76
+ actions: ['iam:PassRole'],
77
+ resources: [securityAgent.serviceRole.roleArn],
78
+ conditions: {
79
+ StringEquals: { 'iam:PassedToService': 'securityagent.amazonaws.com' },
80
+ },
81
+ }),
82
+ new iam.PolicyStatement({
83
+ sid: 'ScanBucketWrite',
84
+ effect: iam.Effect.ALLOW,
85
+ actions: ['s3:PutObject', 's3:GetObject', 's3:ListBucket'],
86
+ resources: [
87
+ securityAgent.scanBucket.bucketArn,
88
+ securityAgent.scanBucket.arnForObjects('*'),
89
+ ],
90
+ }),
91
+ new iam.PolicyStatement({
92
+ sid: 'KMSForScanBucket',
93
+ effect: iam.Effect.ALLOW,
94
+ actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
95
+ resources: [kmsKey.keyArn],
96
+ }),
97
+ new iam.PolicyStatement({
98
+ sid: 'SSMReadConfig',
99
+ effect: iam.Effect.ALLOW,
100
+ actions: ['ssm:GetParameter', 'ssm:GetParameters', 'ssm:PutParameter'],
101
+ resources: [
102
+ `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/prism/continuum/*`,
103
+ ],
104
+ }),
105
+ ],
106
+ });
107
+
108
+ NagSuppressions.addResourceSuppressions(
109
+ continuumCiPolicy,
110
+ [
111
+ {
112
+ id: 'AwsSolutions-IAM5',
113
+ reason: 'Agent space ID is dynamic; wildcard scoped to securityagent service resources only',
114
+ appliesTo: [`Resource::arn:aws:securityagent:<AWS::Region>:<AWS::AccountId>:agent-space/*`],
115
+ },
116
+ {
117
+ id: 'AwsSolutions-IAM5',
118
+ reason: 'Scan bucket objects include dynamic diff filenames (SHA-based); wildcard on objects only',
119
+ appliesTo: ['Resource::<SecurityAgentScanBucketB752F4A9.Arn>/*'],
120
+ },
121
+ {
122
+ id: 'AwsSolutions-IAM5',
123
+ reason: 'SSM parameters under /prism/continuum/ are all non-sensitive config values (IDs and bucket names)',
124
+ appliesTo: ['Resource::arn:aws:ssm:<AWS::Region>:<AWS::AccountId>:parameter/prism/continuum/*'],
125
+ },
126
+ ],
127
+ true,
128
+ );
129
+
130
+ new cdk.CfnOutput(scope, 'ContinuumCiPolicyArn', {
131
+ value: continuumCiPolicy.managedPolicyArn,
132
+ description: 'Attach this policy to your GitHub OIDC role for Continuum CI scans',
133
+ });
134
+
135
+ return securityAgent;
136
+ }
@@ -0,0 +1,69 @@
1
+ import * as cdk from 'aws-cdk-lib';
2
+ import * as iam from 'aws-cdk-lib/aws-iam';
3
+ import * as kms from 'aws-cdk-lib/aws-kms';
4
+ import { Construct } from 'constructs';
5
+ import { NagSuppressions } from 'cdk-nag';
6
+ import { SecurityAgentConstruct } from './constructs/security-agent-construct';
7
+ import { addSecurityAgentResources } from './security-agent-resources';
8
+
9
+ export interface SecurityAgentStackProps extends cdk.StackProps {
10
+ /**
11
+ * ARN of an existing KMS key to encrypt the agent space with. Its key policy
12
+ * must already allow securityagent.amazonaws.com to encrypt and decrypt; an
13
+ * imported key's policy cannot be changed from here. Default: a new key.
14
+ */
15
+ kmsKeyArn?: string;
16
+ }
17
+
18
+ /**
19
+ * PrismD1SecurityAgent: AWS Security Agent for PRISM without the metrics
20
+ * pipeline. Same resources, names and exports as the pipeline's
21
+ * enableSecurityAgent=true path, plus its own KMS key. Only for accounts that
22
+ * do not host Security Agent in PrismD1MetricsPipeline; the fixed resource
23
+ * and export names make CloudFormation refuse a second copy.
24
+ *
25
+ * Not here: the security-agent-processor and remediation tracker Lambdas,
26
+ * which read the pipeline's tables and stay in PrismD1MetricsPipeline.
27
+ */
28
+ export class SecurityAgentStack extends cdk.Stack {
29
+ public readonly securityAgent: SecurityAgentConstruct;
30
+
31
+ constructor(scope: Construct, id: string, props: SecurityAgentStackProps = {}) {
32
+ super(scope, id, props);
33
+
34
+ let key: kms.IKey;
35
+ if (props.kmsKeyArn) {
36
+ key = kms.Key.fromKeyArn(this, 'SecurityAgentKey', props.kmsKeyArn);
37
+ } else {
38
+ // The same grants the pipeline's PrismDataKey gives Security Agent.
39
+ const k = new kms.Key(this, 'SecurityAgentKey', {
40
+ alias: 'alias/prism-d1-security-agent-key',
41
+ description: 'Encryption key for the PRISM D1 Security Agent space',
42
+ enableKeyRotation: true,
43
+ removalPolicy: cdk.RemovalPolicy.RETAIN,
44
+ });
45
+ k.grantEncryptDecrypt(new iam.ServicePrincipal('securityagent.amazonaws.com'));
46
+ k.addToResourcePolicy(new iam.PolicyStatement({
47
+ effect: iam.Effect.ALLOW,
48
+ principals: [new iam.ServicePrincipal('logs.amazonaws.com')],
49
+ actions: ['kms:Encrypt', 'kms:Decrypt', 'kms:GenerateDataKey*', 'kms:DescribeKey'],
50
+ resources: ['*'],
51
+ conditions: {
52
+ ArnLike: {
53
+ 'kms:EncryptionContext:aws:logs:arn': `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/securityagent/*`,
54
+ },
55
+ },
56
+ }));
57
+ key = k;
58
+ }
59
+
60
+ this.securityAgent = addSecurityAgentResources(this, key);
61
+
62
+ // The pipeline stack carries the same suppression stack-wide.
63
+ NagSuppressions.addStackSuppressions(this, [{
64
+ id: 'AwsSolutions-IAM5',
65
+ reason: 'CDK KMS grant generates kms:GenerateDataKey* and kms:ReEncrypt* wildcard actions which are required for envelope encryption',
66
+ appliesTo: ['Action::kms:GenerateDataKey*', 'Action::kms:ReEncrypt*'],
67
+ }]);
68
+ }
69
+ }
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ import * as cdk from 'aws-cdk-lib';
3
+ import { PentestTargetStack } from '../lib/pentest-target-stack';
4
+
5
+ const app = new cdk.App();
6
+ const slug = String(app.node.tryGetContext('slug') || '');
7
+ if (!slug) throw new Error('pentest-cdk: context "slug" is required (the adapter sets it)');
8
+ // The adapter addresses this stack by the same name (PrismPentest-<slug>).
9
+ new PentestTargetStack(app, `PrismPentest-${slug}`, {
10
+ env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
11
+ description: 'prism-cli security pentest target: private VPC + Fargate + internal ALB (temporary)',
12
+ });
@@ -0,0 +1,7 @@
1
+ {
2
+ "app": "npx ts-node --prefer-ts-exts bin/app.ts",
3
+ "context": {
4
+ "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
5
+ "@aws-cdk/core:checkSecretUsage": true
6
+ }
7
+ }