@aws/nx-plugin 1.0.0-rc.19 → 1.0.0-rc.20

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aws/nx-plugin",
3
- "version": "1.0.0-rc.19",
3
+ "version": "1.0.0-rc.20",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/awslabs/nx-plugin-for-aws.git",
@@ -922,8 +922,7 @@ resource "aws_ecr_repository" "agent_core_repository" {
922
922
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
923
923
  name = "\${lower(var.agent_runtime_name)}_repository_\${random_id.unique_suffix.hex}"
924
924
 
925
- #checkov:skip=CKV_AWS_51:Image tag is reused for latest deployments
926
- image_tag_mutability = "MUTABLE"
925
+ image_tag_mutability = "IMMUTABLE"
927
926
  force_delete = true
928
927
 
929
928
  image_scanning_configuration {
@@ -1097,12 +1096,18 @@ data "external" "docker_digest" {
1097
1096
  program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
1098
1097
  }
1099
1098
 
1099
+ locals {
1100
+ # Content-based, immutable image tag derived from the local image digest
1101
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
1102
+ }
1103
+
1100
1104
  # Null resource for container image publish
1101
1105
  resource "null_resource" "docker_publish" {
1102
1106
  triggers = {
1103
1107
  docker_digest = data.external.docker_digest.result.digest
1104
1108
  repository_url = aws_ecr_repository.agent_core_repository.repository_url
1105
1109
  docker_image_tag = var.docker_image_tag
1110
+ image_tag = local.image_tag
1106
1111
  }
1107
1112
 
1108
1113
  provisioner "local-exec" {
@@ -1110,16 +1115,17 @@ resource "null_resource" "docker_publish" {
1110
1115
  # Get ECR login token
1111
1116
  aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REPOSITORY_URL"
1112
1117
 
1113
- # Tag the image
1114
- docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
1118
+ # Tag the image with the immutable content-based tag
1119
+ docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
1115
1120
 
1116
1121
  # Push the image
1117
- docker push "$REPOSITORY_URL:latest"
1122
+ docker push "$REPOSITORY_URL:$IMAGE_TAG"
1118
1123
  EOT
1119
1124
  environment = {
1120
1125
  AWS_REGION = local.aws_region
1121
1126
  REPOSITORY_URL = self.triggers.repository_url
1122
1127
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
1128
+ IMAGE_TAG = self.triggers.image_tag
1123
1129
  }
1124
1130
  }
1125
1131
 
@@ -1134,7 +1140,7 @@ resource "aws_bedrockagentcore_agent_runtime" "agent_runtime" {
1134
1140
 
1135
1141
  agent_runtime_artifact {
1136
1142
  container_configuration {
1137
- container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:latest"
1143
+ container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:\${local.image_tag}"
1138
1144
  }
1139
1145
  }
1140
1146
 
@@ -226,8 +226,7 @@ resource "aws_ecr_repository" "agent_core_repository" {
226
226
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
227
227
  name = "\${lower(var.agent_runtime_name)}_repository_\${random_id.unique_suffix.hex}"
228
228
 
229
- #checkov:skip=CKV_AWS_51:Image tag is reused for latest deployments
230
- image_tag_mutability = "MUTABLE"
229
+ image_tag_mutability = "IMMUTABLE"
231
230
  force_delete = true
232
231
 
233
232
  image_scanning_configuration {
@@ -401,12 +400,18 @@ data "external" "docker_digest" {
401
400
  program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
402
401
  }
403
402
 
403
+ locals {
404
+ # Content-based, immutable image tag derived from the local image digest
405
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
406
+ }
407
+
404
408
  # Null resource for container image publish
405
409
  resource "null_resource" "docker_publish" {
406
410
  triggers = {
407
411
  docker_digest = data.external.docker_digest.result.digest
408
412
  repository_url = aws_ecr_repository.agent_core_repository.repository_url
409
413
  docker_image_tag = var.docker_image_tag
414
+ image_tag = local.image_tag
410
415
  }
411
416
 
412
417
  provisioner "local-exec" {
@@ -414,16 +419,17 @@ resource "null_resource" "docker_publish" {
414
419
  # Get ECR login token
415
420
  aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REPOSITORY_URL"
416
421
 
417
- # Tag the image
418
- docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
422
+ # Tag the image with the immutable content-based tag
423
+ docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
419
424
 
420
425
  # Push the image
421
- docker push "$REPOSITORY_URL:latest"
426
+ docker push "$REPOSITORY_URL:$IMAGE_TAG"
422
427
  EOT
423
428
  environment = {
424
429
  AWS_REGION = local.aws_region
425
430
  REPOSITORY_URL = self.triggers.repository_url
426
431
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
432
+ IMAGE_TAG = self.triggers.image_tag
427
433
  }
428
434
  }
429
435
 
@@ -438,7 +444,7 @@ resource "aws_bedrockagentcore_agent_runtime" "agent_runtime" {
438
444
 
439
445
  agent_runtime_artifact {
440
446
  container_configuration {
441
- container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:latest"
447
+ container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:\${local.image_tag}"
442
448
  }
443
449
  }
444
450
 
@@ -918,8 +918,7 @@ resource "aws_ecr_repository" "agent_core_repository" {
918
918
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
919
919
  name = "\${lower(var.agent_runtime_name)}_repository_\${random_id.unique_suffix.hex}"
920
920
 
921
- #checkov:skip=CKV_AWS_51:Image tag is reused for latest deployments
922
- image_tag_mutability = "MUTABLE"
921
+ image_tag_mutability = "IMMUTABLE"
923
922
  force_delete = true
924
923
 
925
924
  image_scanning_configuration {
@@ -1093,12 +1092,18 @@ data "external" "docker_digest" {
1093
1092
  program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
1094
1093
  }
1095
1094
 
1095
+ locals {
1096
+ # Content-based, immutable image tag derived from the local image digest
1097
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
1098
+ }
1099
+
1096
1100
  # Null resource for container image publish
1097
1101
  resource "null_resource" "docker_publish" {
1098
1102
  triggers = {
1099
1103
  docker_digest = data.external.docker_digest.result.digest
1100
1104
  repository_url = aws_ecr_repository.agent_core_repository.repository_url
1101
1105
  docker_image_tag = var.docker_image_tag
1106
+ image_tag = local.image_tag
1102
1107
  }
1103
1108
 
1104
1109
  provisioner "local-exec" {
@@ -1106,16 +1111,17 @@ resource "null_resource" "docker_publish" {
1106
1111
  # Get ECR login token
1107
1112
  aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REPOSITORY_URL"
1108
1113
 
1109
- # Tag the image
1110
- docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
1114
+ # Tag the image with the immutable content-based tag
1115
+ docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
1111
1116
 
1112
1117
  # Push the image
1113
- docker push "$REPOSITORY_URL:latest"
1118
+ docker push "$REPOSITORY_URL:$IMAGE_TAG"
1114
1119
  EOT
1115
1120
  environment = {
1116
1121
  AWS_REGION = local.aws_region
1117
1122
  REPOSITORY_URL = self.triggers.repository_url
1118
1123
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
1124
+ IMAGE_TAG = self.triggers.image_tag
1119
1125
  }
1120
1126
  }
1121
1127
 
@@ -1130,7 +1136,7 @@ resource "aws_bedrockagentcore_agent_runtime" "agent_runtime" {
1130
1136
 
1131
1137
  agent_runtime_artifact {
1132
1138
  container_configuration {
1133
- container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:latest"
1139
+ container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:\${local.image_tag}"
1134
1140
  }
1135
1141
  }
1136
1142
 
@@ -226,8 +226,7 @@ resource "aws_ecr_repository" "agent_core_repository" {
226
226
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
227
227
  name = "\${lower(var.agent_runtime_name)}_repository_\${random_id.unique_suffix.hex}"
228
228
 
229
- #checkov:skip=CKV_AWS_51:Image tag is reused for latest deployments
230
- image_tag_mutability = "MUTABLE"
229
+ image_tag_mutability = "IMMUTABLE"
231
230
  force_delete = true
232
231
 
233
232
  image_scanning_configuration {
@@ -401,12 +400,18 @@ data "external" "docker_digest" {
401
400
  program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
402
401
  }
403
402
 
403
+ locals {
404
+ # Content-based, immutable image tag derived from the local image digest
405
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
406
+ }
407
+
404
408
  # Null resource for container image publish
405
409
  resource "null_resource" "docker_publish" {
406
410
  triggers = {
407
411
  docker_digest = data.external.docker_digest.result.digest
408
412
  repository_url = aws_ecr_repository.agent_core_repository.repository_url
409
413
  docker_image_tag = var.docker_image_tag
414
+ image_tag = local.image_tag
410
415
  }
411
416
 
412
417
  provisioner "local-exec" {
@@ -414,16 +419,17 @@ resource "null_resource" "docker_publish" {
414
419
  # Get ECR login token
415
420
  aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REPOSITORY_URL"
416
421
 
417
- # Tag the image
418
- docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
422
+ # Tag the image with the immutable content-based tag
423
+ docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
419
424
 
420
425
  # Push the image
421
- docker push "$REPOSITORY_URL:latest"
426
+ docker push "$REPOSITORY_URL:$IMAGE_TAG"
422
427
  EOT
423
428
  environment = {
424
429
  AWS_REGION = local.aws_region
425
430
  REPOSITORY_URL = self.triggers.repository_url
426
431
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
432
+ IMAGE_TAG = self.triggers.image_tag
427
433
  }
428
434
  }
429
435
 
@@ -438,7 +444,7 @@ resource "aws_bedrockagentcore_agent_runtime" "agent_runtime" {
438
444
 
439
445
  agent_runtime_artifact {
440
446
  container_configuration {
441
- container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:latest"
447
+ container_uri = "\${aws_ecr_repository.agent_core_repository.repository_url}:\${local.image_tag}"
442
448
  }
443
449
  }
444
450
 
@@ -1782,9 +1782,8 @@ module "add_rdb_to_runtime_config" {
1782
1782
 
1783
1783
  resource "aws_ecr_repository" "migration_handler" {
1784
1784
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
1785
- #checkov:skip=CKV_AWS_51:Mutable tags are intentional; the migration handler is always pushed as latest and redeployment is gated on docker_digest changes in null_resource.docker_publish
1786
1785
  name = "db-migration-\${random_string.suffix.result}"
1787
- image_tag_mutability = "MUTABLE"
1786
+ image_tag_mutability = "IMMUTABLE"
1788
1787
  force_delete = true
1789
1788
 
1790
1789
  image_scanning_configuration {
@@ -1835,23 +1834,30 @@ data "external" "docker_digest" {
1835
1834
  program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
1836
1835
  }
1837
1836
 
1837
+ locals {
1838
+ # Content-based, immutable image tag derived from the local image digest
1839
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
1840
+ }
1841
+
1838
1842
  resource "null_resource" "docker_publish" {
1839
1843
  triggers = {
1840
1844
  docker_digest = data.external.docker_digest.result.digest
1841
1845
  repository_url = aws_ecr_repository.migration_handler.repository_url
1842
1846
  docker_image_tag = var.docker_image_tag
1847
+ image_tag = local.image_tag
1843
1848
  }
1844
1849
 
1845
1850
  provisioner "local-exec" {
1846
1851
  command = <<-EOT
1847
1852
  aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REPOSITORY_URL"
1848
- docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
1849
- docker push "$REPOSITORY_URL:latest"
1853
+ docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
1854
+ docker push "$REPOSITORY_URL:$IMAGE_TAG"
1850
1855
  EOT
1851
1856
  environment = {
1852
1857
  AWS_REGION = data.aws_region.current.region
1853
1858
  REPOSITORY_URL = self.triggers.repository_url
1854
1859
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
1860
+ IMAGE_TAG = self.triggers.image_tag
1855
1861
  }
1856
1862
  }
1857
1863
 
@@ -2141,7 +2147,7 @@ resource "aws_lambda_function" "migration_handler" {
2141
2147
  package_type = "Image"
2142
2148
  function_name = local.migration_function_name
2143
2149
  role = aws_iam_role.migration_handler.arn
2144
- image_uri = "\${aws_ecr_repository.migration_handler.repository_url}:latest"
2150
+ image_uri = "\${aws_ecr_repository.migration_handler.repository_url}:\${local.image_tag}"
2145
2151
  memory_size = 1024
2146
2152
  timeout = 300
2147
2153
  architectures = ["arm64"]
@@ -3263,9 +3269,8 @@ module "add_rdb_to_runtime_config" {
3263
3269
 
3264
3270
  resource "aws_ecr_repository" "migration_handler" {
3265
3271
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
3266
- #checkov:skip=CKV_AWS_51:Mutable tags are intentional; the migration handler is always pushed as latest and redeployment is gated on docker_digest changes in null_resource.docker_publish
3267
3272
  name = "db-migration-\${random_string.suffix.result}"
3268
- image_tag_mutability = "MUTABLE"
3273
+ image_tag_mutability = "IMMUTABLE"
3269
3274
  force_delete = true
3270
3275
 
3271
3276
  image_scanning_configuration {
@@ -3316,23 +3321,30 @@ data "external" "docker_digest" {
3316
3321
  program = ["sh", "-c", "echo '{\\"digest\\":\\"'$(docker inspect \${var.docker_image_tag} --format '{{.Id}}')'\\"}' "]
3317
3322
  }
3318
3323
 
3324
+ locals {
3325
+ # Content-based, immutable image tag derived from the local image digest
3326
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
3327
+ }
3328
+
3319
3329
  resource "null_resource" "docker_publish" {
3320
3330
  triggers = {
3321
3331
  docker_digest = data.external.docker_digest.result.digest
3322
3332
  repository_url = aws_ecr_repository.migration_handler.repository_url
3323
3333
  docker_image_tag = var.docker_image_tag
3334
+ image_tag = local.image_tag
3324
3335
  }
3325
3336
 
3326
3337
  provisioner "local-exec" {
3327
3338
  command = <<-EOT
3328
3339
  aws ecr get-login-password --region "$AWS_REGION" | docker login --username AWS --password-stdin "$REPOSITORY_URL"
3329
- docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
3330
- docker push "$REPOSITORY_URL:latest"
3340
+ docker tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
3341
+ docker push "$REPOSITORY_URL:$IMAGE_TAG"
3331
3342
  EOT
3332
3343
  environment = {
3333
3344
  AWS_REGION = data.aws_region.current.region
3334
3345
  REPOSITORY_URL = self.triggers.repository_url
3335
3346
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
3347
+ IMAGE_TAG = self.triggers.image_tag
3336
3348
  }
3337
3349
  }
3338
3350
 
@@ -3627,7 +3639,7 @@ resource "aws_lambda_function" "migration_handler" {
3627
3639
  package_type = "Image"
3628
3640
  function_name = local.migration_function_name
3629
3641
  role = aws_iam_role.migration_handler.arn
3630
- image_uri = "\${aws_ecr_repository.migration_handler.repository_url}:latest"
3642
+ image_uri = "\${aws_ecr_repository.migration_handler.repository_url}:\${local.image_tag}"
3631
3643
  memory_size = 1024
3632
3644
  timeout = 300
3633
3645
  architectures = ["arm64"]
@@ -160,6 +160,15 @@ variable "website_file_path" {
160
160
  type = string
161
161
  }
162
162
 
163
+ # Content-Security-Policy enforced on all responses. Restricts scripts and
164
+ # framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
165
+ # (connect-src) to AWS service endpoints such as API Gateway, Cognito and
166
+ # Bedrock AgentCore which are only known at deploy time. Edit this to tighten
167
+ # connect-src to your specific origins once they are known.
168
+ locals {
169
+ content_security_policy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
170
+ }
171
+
163
172
 
164
173
  # Data sources
165
174
  data "aws_caller_identity" "current" {}
@@ -520,8 +529,7 @@ resource "aws_cloudfront_origin_access_control" "website_oac" {
520
529
  signing_protocol = "sigv4"
521
530
  }
522
531
 
523
- # Security headers applied to all responses. A Content-Security-Policy is
524
- # intentionally omitted as it is application specific - add one for your app.
532
+ # Security headers applied to all responses.
525
533
  resource "aws_cloudfront_response_headers_policy" "website" {
526
534
  name = "\${aws_s3_bucket.website.bucket}-security-headers"
527
535
 
@@ -543,6 +551,10 @@ resource "aws_cloudfront_response_headers_policy" "website" {
543
551
  referrer_policy = "strict-origin-when-cross-origin"
544
552
  override = true
545
553
  }
554
+ content_security_policy {
555
+ content_security_policy = local.content_security_policy
556
+ override = true
557
+ }
546
558
  }
547
559
  }
548
560
 
@@ -1570,6 +1582,23 @@ import { suppressRules } from './checkov.js';
1570
1582
 
1571
1583
  const DEFAULT_RUNTIME_CONFIG_FILENAME = 'runtime-config.json';
1572
1584
 
1585
+ // Content-Security-Policy enforced on all responses. Restricts scripts and
1586
+ // framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
1587
+ // (connect-src) to AWS service endpoints such as API Gateway, Cognito and
1588
+ // Bedrock AgentCore which are only known at deploy time. Edit this to tighten
1589
+ // connect-src to your specific origins once they are known.
1590
+ const CONTENT_SECURITY_POLICY = [
1591
+ "default-src 'self'",
1592
+ "script-src 'self'",
1593
+ "style-src 'self' 'unsafe-inline'",
1594
+ "img-src 'self' data:",
1595
+ "font-src 'self' data:",
1596
+ "connect-src 'self' https: wss:",
1597
+ "object-src 'none'",
1598
+ "base-uri 'self'",
1599
+ "frame-ancestors 'none'",
1600
+ ].join('; ');
1601
+
1573
1602
  export interface StaticWebsiteProps {
1574
1603
  readonly websiteName: string;
1575
1604
  readonly websiteFilePath: string;
@@ -1661,8 +1690,7 @@ export class StaticWebsite extends Construct {
1661
1690
  'Distribution log bucket does not need versioning enabled',
1662
1691
  );
1663
1692
 
1664
- // Security headers applied to all responses. A Content-Security-Policy is
1665
- // intentionally omitted as it is application specific - add one for your app.
1693
+ // Security headers applied to all responses.
1666
1694
  const responseHeadersPolicy = new ResponseHeadersPolicy(
1667
1695
  this,
1668
1696
  'ResponseHeadersPolicy',
@@ -1684,6 +1712,10 @@ export class StaticWebsite extends Construct {
1684
1712
  HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
1685
1713
  override: true,
1686
1714
  },
1715
+ contentSecurityPolicy: {
1716
+ contentSecurityPolicy: CONTENT_SECURITY_POLICY,
1717
+ override: true,
1718
+ },
1687
1719
  },
1688
1720
  },
1689
1721
  );
@@ -3197,6 +3229,23 @@ import { suppressRules } from './checkov.js';
3197
3229
 
3198
3230
  const DEFAULT_RUNTIME_CONFIG_FILENAME = 'runtime-config.json';
3199
3231
 
3232
+ // Content-Security-Policy enforced on all responses. Restricts scripts and
3233
+ // framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
3234
+ // (connect-src) to AWS service endpoints such as API Gateway, Cognito and
3235
+ // Bedrock AgentCore which are only known at deploy time. Edit this to tighten
3236
+ // connect-src to your specific origins once they are known.
3237
+ const CONTENT_SECURITY_POLICY = [
3238
+ "default-src 'self'",
3239
+ "script-src 'self'",
3240
+ "style-src 'self' 'unsafe-inline'",
3241
+ "img-src 'self' data:",
3242
+ "font-src 'self' data:",
3243
+ "connect-src 'self' https: wss:",
3244
+ "object-src 'none'",
3245
+ "base-uri 'self'",
3246
+ "frame-ancestors 'none'",
3247
+ ].join('; ');
3248
+
3200
3249
  export interface StaticWebsiteProps {
3201
3250
  readonly websiteName: string;
3202
3251
  readonly websiteFilePath: string;
@@ -3288,8 +3337,7 @@ export class StaticWebsite extends Construct {
3288
3337
  'Distribution log bucket does not need versioning enabled',
3289
3338
  );
3290
3339
 
3291
- // Security headers applied to all responses. A Content-Security-Policy is
3292
- // intentionally omitted as it is application specific - add one for your app.
3340
+ // Security headers applied to all responses.
3293
3341
  const responseHeadersPolicy = new ResponseHeadersPolicy(
3294
3342
  this,
3295
3343
  'ResponseHeadersPolicy',
@@ -3311,6 +3359,10 @@ export class StaticWebsite extends Construct {
3311
3359
  HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
3312
3360
  override: true,
3313
3361
  },
3362
+ contentSecurityPolicy: {
3363
+ contentSecurityPolicy: CONTENT_SECURITY_POLICY,
3364
+ override: true,
3365
+ },
3314
3366
  },
3315
3367
  },
3316
3368
  );
@@ -4743,6 +4795,23 @@ import { suppressRules } from './checkov.js';
4743
4795
 
4744
4796
  const DEFAULT_RUNTIME_CONFIG_FILENAME = 'runtime-config.json';
4745
4797
 
4798
+ // Content-Security-Policy enforced on all responses. Restricts scripts and
4799
+ // framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
4800
+ // (connect-src) to AWS service endpoints such as API Gateway, Cognito and
4801
+ // Bedrock AgentCore which are only known at deploy time. Edit this to tighten
4802
+ // connect-src to your specific origins once they are known.
4803
+ const CONTENT_SECURITY_POLICY = [
4804
+ "default-src 'self'",
4805
+ "script-src 'self'",
4806
+ "style-src 'self' 'unsafe-inline'",
4807
+ "img-src 'self' data:",
4808
+ "font-src 'self' data:",
4809
+ "connect-src 'self' https: wss:",
4810
+ "object-src 'none'",
4811
+ "base-uri 'self'",
4812
+ "frame-ancestors 'none'",
4813
+ ].join('; ');
4814
+
4746
4815
  export interface StaticWebsiteProps {
4747
4816
  readonly websiteName: string;
4748
4817
  readonly websiteFilePath: string;
@@ -4834,8 +4903,7 @@ export class StaticWebsite extends Construct {
4834
4903
  'Distribution log bucket does not need versioning enabled',
4835
4904
  );
4836
4905
 
4837
- // Security headers applied to all responses. A Content-Security-Policy is
4838
- // intentionally omitted as it is application specific - add one for your app.
4906
+ // Security headers applied to all responses.
4839
4907
  const responseHeadersPolicy = new ResponseHeadersPolicy(
4840
4908
  this,
4841
4909
  'ResponseHeadersPolicy',
@@ -4857,6 +4925,10 @@ export class StaticWebsite extends Construct {
4857
4925
  HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
4858
4926
  override: true,
4859
4927
  },
4928
+ contentSecurityPolicy: {
4929
+ contentSecurityPolicy: CONTENT_SECURITY_POLICY,
4930
+ override: true,
4931
+ },
4860
4932
  },
4861
4933
  },
4862
4934
  );
@@ -95,8 +95,7 @@ resource "aws_ecr_repository" "agent_core_repository" {
95
95
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
96
96
  name = "${lower(var.agent_runtime_name)}_repository_${random_id.unique_suffix.hex}"
97
97
 
98
- #checkov:skip=CKV_AWS_51:Image tag is reused for latest deployments
99
- image_tag_mutability = "MUTABLE"
98
+ image_tag_mutability = "IMMUTABLE"
100
99
  force_delete = true
101
100
 
102
101
  image_scanning_configuration {
@@ -270,12 +269,18 @@ data "external" "docker_digest" {
270
269
  program = ["sh", "-c", "echo '{\"digest\":\"'$(<%= containers %> inspect ${var.docker_image_tag} --format '{{.Id}}')'\"}' "]
271
270
  }
272
271
 
272
+ locals {
273
+ # Content-based, immutable image tag derived from the local image digest
274
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
275
+ }
276
+
273
277
  # Null resource for container image publish
274
278
  resource "null_resource" "docker_publish" {
275
279
  triggers = {
276
280
  docker_digest = data.external.docker_digest.result.digest
277
281
  repository_url = aws_ecr_repository.agent_core_repository.repository_url
278
282
  docker_image_tag = var.docker_image_tag
283
+ image_tag = local.image_tag
279
284
  }
280
285
 
281
286
  provisioner "local-exec" {
@@ -283,16 +288,17 @@ resource "null_resource" "docker_publish" {
283
288
  # Get ECR login token
284
289
  aws ecr get-login-password --region "$AWS_REGION" | <%= containers %> login --username AWS --password-stdin "$REPOSITORY_URL"
285
290
 
286
- # Tag the image
287
- <%= containers %> tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
291
+ # Tag the image with the immutable content-based tag
292
+ <%= containers %> tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
288
293
 
289
294
  # Push the image
290
- <%= containers %> push "$REPOSITORY_URL:latest"
295
+ <%= containers %> push "$REPOSITORY_URL:$IMAGE_TAG"
291
296
  EOT
292
297
  environment = {
293
298
  AWS_REGION = local.aws_region
294
299
  REPOSITORY_URL = self.triggers.repository_url
295
300
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
301
+ IMAGE_TAG = self.triggers.image_tag
296
302
  }
297
303
  }
298
304
 
@@ -307,7 +313,7 @@ resource "aws_bedrockagentcore_agent_runtime" "agent_runtime" {
307
313
 
308
314
  agent_runtime_artifact {
309
315
  container_configuration {
310
- container_uri = "${aws_ecr_repository.agent_core_repository.repository_url}:latest"
316
+ container_uri = "${aws_ecr_repository.agent_core_repository.repository_url}:${local.image_tag}"
311
317
  }
312
318
  }
313
319
 
@@ -225,9 +225,8 @@ module "add_rdb_to_runtime_config" {
225
225
 
226
226
  resource "aws_ecr_repository" "migration_handler" {
227
227
  #checkov:skip=CKV_AWS_136:AES256 encryption is sufficient for ECR repositories
228
- #checkov:skip=CKV_AWS_51:Mutable tags are intentional; the migration handler is always pushed as latest and redeployment is gated on docker_digest changes in null_resource.docker_publish
229
228
  name = "<%= nameKebabCase %>-migration-${random_string.suffix.result}"
230
- image_tag_mutability = "MUTABLE"
229
+ image_tag_mutability = "IMMUTABLE"
231
230
  force_delete = true
232
231
 
233
232
  image_scanning_configuration {
@@ -278,23 +277,30 @@ data "external" "docker_digest" {
278
277
  program = ["sh", "-c", "echo '{\"digest\":\"'$(<%= containerEngine %> inspect ${var.docker_image_tag} --format '{{.Id}}')'\"}' "]
279
278
  }
280
279
 
280
+ locals {
281
+ # Content-based, immutable image tag derived from the local image digest
282
+ image_tag = replace(data.external.docker_digest.result.digest, "sha256:", "")
283
+ }
284
+
281
285
  resource "null_resource" "docker_publish" {
282
286
  triggers = {
283
287
  docker_digest = data.external.docker_digest.result.digest
284
288
  repository_url = aws_ecr_repository.migration_handler.repository_url
285
289
  docker_image_tag = var.docker_image_tag
290
+ image_tag = local.image_tag
286
291
  }
287
292
 
288
293
  provisioner "local-exec" {
289
294
  command = <<-EOT
290
295
  aws ecr get-login-password --region "$AWS_REGION" | <%= containerEngine %> login --username AWS --password-stdin "$REPOSITORY_URL"
291
- <%= containerEngine %> tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:latest"
292
- <%= containerEngine %> push "$REPOSITORY_URL:latest"
296
+ <%= containerEngine %> tag "$DOCKER_IMAGE_TAG" "$REPOSITORY_URL:$IMAGE_TAG"
297
+ <%= containerEngine %> push "$REPOSITORY_URL:$IMAGE_TAG"
293
298
  EOT
294
299
  environment = {
295
300
  AWS_REGION = data.aws_region.current.region
296
301
  REPOSITORY_URL = self.triggers.repository_url
297
302
  DOCKER_IMAGE_TAG = self.triggers.docker_image_tag
303
+ IMAGE_TAG = self.triggers.image_tag
298
304
  }
299
305
  }
300
306
 
@@ -610,7 +616,7 @@ resource "aws_lambda_function" "migration_handler" {
610
616
  package_type = "Image"
611
617
  function_name = local.migration_function_name
612
618
  role = aws_iam_role.migration_handler.arn
613
- image_uri = "${aws_ecr_repository.migration_handler.repository_url}:latest"
619
+ image_uri = "${aws_ecr_repository.migration_handler.repository_url}:${local.image_tag}"
614
620
  memory_size = 1024
615
621
  timeout = 300
616
622
  architectures = ["arm64"]
@@ -27,6 +27,23 @@ import { suppressRules } from './checkov.js';
27
27
 
28
28
  const DEFAULT_RUNTIME_CONFIG_FILENAME = 'runtime-config.json';
29
29
 
30
+ // Content-Security-Policy enforced on all responses. Restricts scripts and
31
+ // framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
32
+ // (connect-src) to AWS service endpoints such as API Gateway, Cognito and
33
+ // Bedrock AgentCore which are only known at deploy time. Edit this to tighten
34
+ // connect-src to your specific origins once they are known.
35
+ const CONTENT_SECURITY_POLICY = [
36
+ "default-src 'self'",
37
+ "script-src 'self'",
38
+ "style-src 'self' 'unsafe-inline'",
39
+ "img-src 'self' data:",
40
+ "font-src 'self' data:",
41
+ "connect-src 'self' https: wss:",
42
+ "object-src 'none'",
43
+ "base-uri 'self'",
44
+ "frame-ancestors 'none'",
45
+ ].join('; ');
46
+
30
47
  export interface StaticWebsiteProps {
31
48
  readonly websiteName: string;
32
49
  readonly websiteFilePath: string;
@@ -118,8 +135,7 @@ export class StaticWebsite extends Construct {
118
135
  'Distribution log bucket does not need versioning enabled',
119
136
  );
120
137
 
121
- // Security headers applied to all responses. A Content-Security-Policy is
122
- // intentionally omitted as it is application specific - add one for your app.
138
+ // Security headers applied to all responses.
123
139
  const responseHeadersPolicy = new ResponseHeadersPolicy(
124
140
  this,
125
141
  'ResponseHeadersPolicy',
@@ -140,6 +156,10 @@ export class StaticWebsite extends Construct {
140
156
  referrerPolicy: HeadersReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN,
141
157
  override: true,
142
158
  },
159
+ contentSecurityPolicy: {
160
+ contentSecurityPolicy: CONTENT_SECURITY_POLICY,
161
+ override: true,
162
+ },
143
163
  },
144
164
  },
145
165
  );
@@ -23,6 +23,15 @@ variable "website_file_path" {
23
23
  type = string
24
24
  }
25
25
 
26
+ # Content-Security-Policy enforced on all responses. Restricts scripts and
27
+ # framing to mitigate XSS and clickjacking, while permitting HTTPS/WSS calls
28
+ # (connect-src) to AWS service endpoints such as API Gateway, Cognito and
29
+ # Bedrock AgentCore which are only known at deploy time. Edit this to tighten
30
+ # connect-src to your specific origins once they are known.
31
+ locals {
32
+ content_security_policy = "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self' https: wss:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'"
33
+ }
34
+
26
35
 
27
36
  # Data sources
28
37
  data "aws_caller_identity" "current" {}
@@ -383,8 +392,7 @@ resource "aws_cloudfront_origin_access_control" "website_oac" {
383
392
  signing_protocol = "sigv4"
384
393
  }
385
394
 
386
- # Security headers applied to all responses. A Content-Security-Policy is
387
- # intentionally omitted as it is application specific - add one for your app.
395
+ # Security headers applied to all responses.
388
396
  resource "aws_cloudfront_response_headers_policy" "website" {
389
397
  name = "${aws_s3_bucket.website.bucket}-security-headers"
390
398
 
@@ -406,6 +414,10 @@ resource "aws_cloudfront_response_headers_policy" "website" {
406
414
  referrer_policy = "strict-origin-when-cross-origin"
407
415
  override = true
408
416
  }
417
+ content_security_policy {
418
+ content_security_policy = local.content_security_policy
419
+ override = true
420
+ }
409
421
  }
410
422
  }
411
423