diff --git a/terraform/codebase-pipelines/ecr.tf b/terraform/codebase-pipelines/ecr.tf index fff9829c9..bfee637d9 100644 --- a/terraform/codebase-pipelines/ecr.tf +++ b/terraform/codebase-pipelines/ecr.tf @@ -19,46 +19,38 @@ resource "aws_ecr_repository_policy" "ecr_policy" { } data "aws_iam_policy_document" "ecr_policy" { - statement { - effect = "Allow" + statement { + sid = "PreventRepoDelete" + effect = "Deny" actions = [ - "ecr:BatchCheckLayerAvailability", - "ecr:BatchGetImage", - "ecr:CompleteLayerUpload", - "ecr:GetDownloadUrlForLayer", - "ecr:InitiateLayerUpload", - "ecr:PutImage", - "ecr:UploadLayerPart" + "ecr:DeleteRepository" ] - principals { - type = "AWS" - identifiers = [ - for id in local.deploy_account_ids : - "arn:aws:iam::${id}:root" - ] + type = "AWS" + identifiers = ["*"] } } statement { - sid = "PreventRepoDelete" - effect = "Deny" - + sid = "RootPull" + effect = "Allow" actions = [ - "ecr:DeleteRepository" + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload" ] - principals { - type = "AWS" - identifiers = ["*"] + type = "AWS" + identifiers = [ + for id in local.deploy_account_ids : "arn:aws:iam::${id}:root" + ] } } statement { sid = "PreventImageDelete" effect = "Deny" - actions = [ "ecr:BatchDeleteImage" ] @@ -71,10 +63,105 @@ data "aws_iam_policy_document" "ecr_policy" { condition { test = "ArnNotLike" variable = "aws:PrincipalArn" - values = [ + + values = compact([ "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ecr-housekeeping-role", - "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/github-oidc-${var.application}-repo-role" + contains(["dual_codepipeline_github", "github_actions"], var.pipeline_mode) + ? "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/github-oidc-${var.application}-platform-image-build" + : "" + ]) + } + } + + dynamic "statement" { + for_each = var.pipeline_mode == "aws_codepipeline" ? [1] : [] + + content { + effect = "Allow" + actions = [ + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:UploadLayerPart" ] + principals { + type = "AWS" + identifiers = [ + for id in local.deploy_account_ids : + "arn:aws:iam::${id}:root" + ] + } } } -} + + # ========================================== + # Github Actions & Paketo/BYOD policy + # ========================================== + + dynamic "statement" { + for_each = contains(["dual_codepipeline_github"], var.pipeline_mode) ? [1] : [] + + content { + sid = "BasicECRAccess" + effect = "Deny" + actions = [ + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:UploadLayerPart" + ] + principals { + type = "AWS" + identifiers = [ + for id in local.deploy_account_ids : "arn:aws:iam::${id}:root" + ] + } + condition { + test = "ArnNotLike" + variable = "aws:PrincipalArn" + values = flatten([ + for id in local.deploy_account_ids : [ + "arn:aws:iam::${id}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_AdministratorAccess_*", + "arn:aws:iam::${id}:role/github-oidc-${var.application}-platform-image-build", + "arn:aws:iam::${id}:role/${var.application}-${var.codebase}-codebase-image-build" + ] + ]) + } + } + } + + # ========================================== + # Github Actions & BYOD policy + # ========================================== + + + dynamic "statement" { + for_each = (var.pipeline_mode == "github_actions" && var.requires_image_build == false) ? [1] : [] + + content { + sid = "PushActions" + effect = "Deny" + actions = [ + "ecr:CompleteLayerUpload", + "ecr:InitiateLayerUpload", + "ecr:PutImage", + "ecr:UploadLayerPart", + "ecr:BatchCheckLayerAvailability" + ] + principals { + type = "AWS" + identifiers = ["*"] + } + condition { + test = "ArnNotLike" + variable = "aws:PrincipalArn" + values = flatten([ + for id in local.deploy_account_ids : [ + "arn:aws:iam::${id}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_AdministratorAccess_*", + "arn:aws:iam::${id}:role/github-oidc-${var.application}-platform-image-build" + ] + ]) + } + } + } +} \ No newline at end of file diff --git a/terraform/codebase-pipelines/tests/unit.tftest.hcl b/terraform/codebase-pipelines/tests/unit.tftest.hcl index 42b69431c..f4b68884f 100644 --- a/terraform/codebase-pipelines/tests/unit.tftest.hcl +++ b/terraform/codebase-pipelines/tests/unit.tftest.hcl @@ -9,6 +9,17 @@ override_data { } } } + +override_data { + target = data.aws_iam_policy_document.access_artifact_store + values = { + json = jsonencode({ + Version = "2012-10-17" + Statement = [] + }) + } +} + override_data { target = data.aws_iam_policy_document.assume_codebuild_role values = { @@ -396,18 +407,15 @@ run "test_ecr" { error_message = "Should be: {\"Sid\": \"ECRPolicy\"}" } assert { - condition = data.aws_iam_policy_document.ecr_policy.statement[0].effect == "Allow" - error_message = "Should be: Allow" + condition = data.aws_iam_policy_document.ecr_policy.statement[0].effect == "Deny" + error_message = "Should be: Deny" } assert { - condition = data.aws_iam_policy_document.ecr_policy.statement[0].actions == toset([ - "ecr:BatchCheckLayerAvailability", - "ecr:BatchGetImage", - "ecr:CompleteLayerUpload", - "ecr:GetDownloadUrlForLayer", - "ecr:InitiateLayerUpload", - "ecr:PutImage", - "ecr:UploadLayerPart" + condition = toset(flatten([ + for c in data.aws_iam_policy_document.ecr_policy.statement[2].condition : c.values + if c.variable == "aws:PrincipalArn" + ])) == toset([ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ecr-housekeeping-role", ]) error_message = "Unexpected actions" } @@ -416,11 +424,94 @@ run "test_ecr" { error_message = "Should be: AWS" } assert { - condition = flatten([for el in data.aws_iam_policy_document.ecr_policy.statement[0].principals : el.identifiers]) == ["arn:aws:iam::000123456789:root", "arn:aws:iam::123456789000:root"] + condition = flatten([for el in data.aws_iam_policy_document.ecr_policy.statement[0].principals : el.identifiers]) == ["*"] error_message = "ECR policy principals incorrect" } } +run "test_ecr_pipeline_mode_github" { + command = plan + + variables { + pipeline_mode = "github_actions" + requires_image_build = false + application = var.application + codebase = var.codebase + } + + assert { + condition = toset(flatten([ + for s in data.aws_iam_policy_document.ecr_policy.statement : [ + for c in s.condition : c.values + if c.variable == "aws:PrincipalArn" + ] + if s.sid == "PushActions" + ])) == toset(flatten([ + for id in ["000123456789", "123456789000"] : [ + "arn:aws:iam::${id}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_AdministratorAccess_*", + "arn:aws:iam::${id}:role/github-oidc-${var.application}-platform-image-build" + ] + ])) + error_message = "Unexpected values for PushActions" + } + + assert { + condition = toset(flatten([ + for s in data.aws_iam_policy_document.ecr_policy.statement : [ + for c in s.condition : c.values + if c.variable == "aws:PrincipalArn" + ] + if s.sid == "PreventImageDelete" + ])) == toset([ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/github-oidc-${var.application}-platform-image-build", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ecr-housekeeping-role" + ]) + error_message = "Unexpected values for PreventImageDelete" + } +} + +run "test_ecr_pipeline_mode_dual" { + command = plan + + variables { + pipeline_mode = "dual_codepipeline_github" + application = var.application + codebase = var.codebase + } + + assert { + condition = toset(flatten([ + for s in data.aws_iam_policy_document.ecr_policy.statement : [ + for c in s.condition : c.values + if c.variable == "aws:PrincipalArn" + ] + if s.sid == "BasicECRAccess" + ])) == toset(flatten([ + for id in ["000123456789", "123456789000"] : [ + "arn:aws:iam::${id}:role/aws-reserved/sso.amazonaws.com/eu-west-2/AWSReservedSSO_AdministratorAccess_*", + # FIX: Added the platform-image-build role and corrected the codebase-image-build string + "arn:aws:iam::${id}:role/github-oidc-${var.application}-platform-image-build", + "arn:aws:iam::${id}:role/${var.application}-${var.codebase}-codebase-image-build" + ] + ])) + error_message = "Unexpected values for BasicECRAccess" + } + + assert { + condition = toset(flatten([ + for s in data.aws_iam_policy_document.ecr_policy.statement : [ + for c in s.condition : c.values + if c.variable == "aws:PrincipalArn" + ] + if s.sid == "PreventImageDelete" + ])) == toset([ + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/ecr-housekeeping-role", + "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/github-oidc-${var.application}-platform-image-build" + ]) + error_message = "Unexpected values for PreventImageDelete" + } +} + run "test_artifact_store" { command = plan