feat(terraform): complete dev environment adoption (SH-300)

This commit is contained in:
Adam Moussa 2026-09-11 11:22:28 -04:00
parent 8b5281d357
commit f532aa6059
No known key found for this signature in database
7 changed files with 110 additions and 377 deletions

View file

@ -18,17 +18,11 @@ from terraform_import_plan_resources import (
BUCKET_POLICY_ADDRESS = "module.environment_owned.aws_s3_bucket_policy.site" BUCKET_POLICY_ADDRESS = "module.environment_owned.aws_s3_bucket_policy.site"
BUCKET_ADDRESS = "module.environment_owned.aws_s3_bucket.site" BUCKET_ADDRESS = "module.environment_owned.aws_s3_bucket.site"
DEPLOY_POLICY_ADDRESS = (
"module.environment_owned.aws_iam_role_policy.github_deploy"
)
DISTRIBUTION_ADDRESS = ( DISTRIBUTION_ADDRESS = (
"module.environment_owned.aws_cloudfront_distribution.site" "module.environment_owned.aws_cloudfront_distribution.site"
) )
ROLE_ADDRESS = "module.environment_owned.aws_iam_role.github_deploy" ROLE_ADDRESS = "module.environment_owned.aws_iam_role.github_deploy"
TAG_UPDATE_ADDRESSES = CONTROLLED_UPDATE_ADDRESSES - { TAG_UPDATE_ADDRESSES = CONTROLLED_UPDATE_ADDRESSES - {BUCKET_POLICY_ADDRESS}
BUCKET_POLICY_ADDRESS,
DEPLOY_POLICY_ADDRESS,
}
OWNERSHIP_TAGS = { OWNERSHIP_TAGS = {
"Environment": None, "Environment": None,
"ManagedBy": "terraform", "ManagedBy": "terraform",
@ -255,113 +249,6 @@ def _expected_bucket_policy(environment: str, distribution_id: str) -> dict[str,
) )
def _expected_pre_adoption_deploy_policy(
environment: str,
distribution_id: str,
) -> dict[str, Any]:
config = ENVIRONMENT_CONFIG[environment]
bucket_arn = f"arn:aws:s3:::{config['bucket_name']}"
distribution_arn = (
f"arn:aws:cloudfront::396287094661:distribution/{distribution_id}"
)
statements: list[dict[str, Any]] = []
if environment == "dev":
statements.append(
{
"Sid": "AssumeCdkBootstrapRoles",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::396287094661:role/cdk-hnb659fds-*",
}
)
statements.extend(
[
{
"Sid": "DescribeStack",
"Effect": "Allow",
"Action": "cloudformation:DescribeStacks",
"Resource": (
"arn:aws:cloudformation:us-east-1:396287094661:stack/"
f"{config['cloudformation_stack_name']}/*"
),
},
{
"Effect": "Allow",
"Action": [
"s3:Abort*",
"s3:DeleteObject*",
"s3:GetBucket*",
"s3:GetObject*",
"s3:List*",
"s3:PutObject",
"s3:PutObjectLegalHold",
"s3:PutObjectRetention",
"s3:PutObjectTagging",
"s3:PutObjectVersionTagging",
],
"Resource": [bucket_arn, f"{bucket_arn}/*"],
},
{
"Sid": "InvalidateDistribution",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
],
"Resource": distribution_arn,
},
]
)
return _canonical({"Version": "2012-10-17", "Statement": statements})
def _expected_deploy_policy(environment: str, distribution_id: str) -> dict[str, Any]:
bucket = ENVIRONMENT_CONFIG[environment]["bucket_name"]
bucket_arn = f"arn:aws:s3:::{bucket}"
distribution_arn = (
f"arn:aws:cloudfront::396287094661:distribution/{distribution_id}"
)
return _canonical(
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadDeploymentBucket",
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
"s3:ListBucket",
"s3:ListBucketVersions",
],
"Resource": bucket_arn,
},
{
"Sid": "PublishAndRollbackSiteObjects",
"Effect": "Allow",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
],
"Resource": f"{bucket_arn}/*",
},
{
"Sid": "InvalidateDistribution",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
],
"Resource": distribution_arn,
},
],
}
)
def _validate_tag_update( def _validate_tag_update(
address: str, address: str,
before: dict[str, Any], before: dict[str, Any],
@ -432,16 +319,10 @@ def _validate_policy_update(
f"{address}: cannot verify policy without the pinned distribution ID" f"{address}: cannot verify policy without the pinned distribution ID"
) )
return violations return violations
expected_before = ( expected_before = _expected_pre_adoption_bucket_policy(
_expected_pre_adoption_bucket_policy(environment, distribution_id) environment, distribution_id
if address == BUCKET_POLICY_ADDRESS
else _expected_pre_adoption_deploy_policy(environment, distribution_id)
)
expected_after = (
_expected_bucket_policy(environment, distribution_id)
if address == BUCKET_POLICY_ADDRESS
else _expected_deploy_policy(environment, distribution_id)
) )
expected_after = _expected_bucket_policy(environment, distribution_id)
if before_policy is not None and before_policy != expected_before: if before_policy is not None and before_policy != expected_before:
violations.append(f"{address}: pre-adoption policy semantics are not exact") violations.append(f"{address}: pre-adoption policy semantics are not exact")
if after_policy is not None and after_policy != expected_after: if after_policy is not None and after_policy != expected_after:
@ -467,7 +348,7 @@ def _validate_controlled_update(
return [*violations, f"{address}: controlled update requires before/after objects"] return [*violations, f"{address}: controlled update requires before/after objects"]
if address in TAG_UPDATE_ADDRESSES: if address in TAG_UPDATE_ADDRESSES:
violations.extend(_validate_tag_update(address, before, after, environment)) violations.extend(_validate_tag_update(address, before, after, environment))
elif address in {BUCKET_POLICY_ADDRESS, DEPLOY_POLICY_ADDRESS}: elif address == BUCKET_POLICY_ADDRESS:
violations.extend( violations.extend(
_validate_policy_update( _validate_policy_update(
address, address,

View file

@ -45,7 +45,6 @@ CONTROLLED_UPDATE_ADDRESSES = frozenset(
"module.environment_owned.aws_cloudfront_distribution.site", "module.environment_owned.aws_cloudfront_distribution.site",
"module.environment_owned.aws_cloudfront_function.spa_rewrite", "module.environment_owned.aws_cloudfront_function.spa_rewrite",
"module.environment_owned.aws_iam_role.github_deploy", "module.environment_owned.aws_iam_role.github_deploy",
"module.environment_owned.aws_iam_role_policy.github_deploy",
} }
) )

View file

@ -27,7 +27,7 @@ BUCKET = "module.environment_owned.aws_s3_bucket.site"
DEPLOY_POLICY = "module.environment_owned.aws_iam_role_policy.github_deploy" DEPLOY_POLICY = "module.environment_owned.aws_iam_role_policy.github_deploy"
ROLE = "module.environment_owned.aws_iam_role.github_deploy" ROLE = "module.environment_owned.aws_iam_role.github_deploy"
DISTRIBUTION = "module.environment_owned.aws_cloudfront_distribution.site" DISTRIBUTION = "module.environment_owned.aws_cloudfront_distribution.site"
TAG_ADDRESSES = CONTROLLED_UPDATE_ADDRESSES - {BUCKET_POLICY, DEPLOY_POLICY} TAG_ADDRESSES = CONTROLLED_UPDATE_ADDRESSES - {BUCKET_POLICY}
def import_id(environment: str, address: str) -> str: def import_id(environment: str, address: str) -> str:
@ -111,110 +111,6 @@ def bucket_policy(environment: str) -> dict[str, Any]:
} }
def pre_adoption_deploy_policy(environment: str) -> dict[str, Any]:
config = ENVIRONMENT_CONFIG[environment]
bucket_arn = f"arn:aws:s3:::{config['bucket_name']}"
distribution_arn = (
"arn:aws:cloudfront::396287094661:distribution/"
f"{distribution_id(environment)}"
)
statements: list[dict[str, Any]] = []
if environment == "dev":
statements.append(
{
"Sid": "AssumeCdkBootstrapRoles",
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::396287094661:role/cdk-hnb659fds-*",
}
)
statements.extend(
[
{
"Sid": "DescribeStack",
"Effect": "Allow",
"Action": "cloudformation:DescribeStacks",
"Resource": (
"arn:aws:cloudformation:us-east-1:396287094661:stack/"
f"{config['cloudformation_stack_name']}/*"
),
},
{
"Effect": "Allow",
"Action": [
"s3:Abort*",
"s3:DeleteObject*",
"s3:GetBucket*",
"s3:GetObject*",
"s3:List*",
"s3:PutObject",
"s3:PutObjectLegalHold",
"s3:PutObjectRetention",
"s3:PutObjectTagging",
"s3:PutObjectVersionTagging",
],
"Resource": [bucket_arn, f"{bucket_arn}/*"],
},
{
"Sid": "InvalidateDistribution",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
],
"Resource": distribution_arn,
},
]
)
return {"Version": "2012-10-17", "Statement": statements}
def deploy_policy(environment: str) -> dict[str, Any]:
bucket = ENVIRONMENT_CONFIG[environment]["bucket_name"]
bucket_arn = f"arn:aws:s3:::{bucket}"
distribution_arn = (
"arn:aws:cloudfront::396287094661:distribution/"
f"{distribution_id(environment)}"
)
return {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadDeploymentBucket",
"Effect": "Allow",
"Action": [
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
"s3:ListBucket",
"s3:ListBucketVersions",
],
"Resource": bucket_arn,
},
{
"Sid": "PublishAndRollbackSiteObjects",
"Effect": "Allow",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
],
"Resource": f"{bucket_arn}/*",
},
{
"Sid": "InvalidateDistribution",
"Effect": "Allow",
"Action": [
"cloudfront:CreateInvalidation",
"cloudfront:GetInvalidation",
],
"Resource": distribution_arn,
},
],
}
def tag_change(environment: str, address: str) -> dict[str, Any]: def tag_change(environment: str, address: str) -> dict[str, Any]:
manager = { manager = {
"HcpTerraformWorkspace": ENVIRONMENT_CONFIG[environment]["workspace_name"] "HcpTerraformWorkspace": ENVIRONMENT_CONFIG[environment]["workspace_name"]
@ -250,20 +146,12 @@ def tag_change(environment: str, address: str) -> dict[str, Any]:
def policy_change(environment: str, address: str) -> dict[str, Any]: def policy_change(environment: str, address: str) -> dict[str, Any]:
before_policy = ( if address != BUCKET_POLICY:
pre_adoption_bucket_policy(environment) raise AssertionError(f"{address} is not a reviewed policy update")
if address == BUCKET_POLICY
else pre_adoption_deploy_policy(environment)
)
after_policy = (
bucket_policy(environment)
if address == BUCKET_POLICY
else deploy_policy(environment)
)
return { return {
"actions": ["update"], "actions": ["update"],
"before": {"policy": json.dumps(before_policy)}, "before": {"policy": json.dumps(pre_adoption_bucket_policy(environment))},
"after": {"policy": json.dumps(after_policy)}, "after": {"policy": json.dumps(bucket_policy(environment))},
} }
@ -395,9 +283,9 @@ class ImportPlanCheckerTests(unittest.TestCase):
) )
self.assertEqual(["dev"], live_roots) self.assertEqual(["dev"], live_roots)
def test_dev_root_pins_import_phase_in_code(self) -> None: def test_dev_root_pins_adoption_complete_in_code(self) -> None:
source = (REPOSITORY / "terraform/live/dev/main.tf").read_text(encoding="utf-8") source = (REPOSITORY / "terraform/live/dev/main.tf").read_text(encoding="utf-8")
self.assertRegex(source, r"\n\s+adoption_complete\s+= false\n") self.assertRegex(source, r"\n\s+adoption_complete\s+= true\n")
self.assertRegex(source, r"adoption_complete\s+= local\.adoption_complete") self.assertRegex(source, r"adoption_complete\s+= local\.adoption_complete")
self.assertNotIn('variable "adoption_complete"', source) self.assertNotIn('variable "adoption_complete"', source)
for root_file in ("main.tf", "imports.tf", "outputs.tf", "providers.tf", "versions.tf"): for root_file in ("main.tf", "imports.tf", "outputs.tf", "providers.tf", "versions.tf"):
@ -564,54 +452,53 @@ class ImportPlanCheckerTests(unittest.TestCase):
with self.subTest(mutation=mutation): with self.subTest(mutation=mutation):
self.assert_fails(plan, "dev", BUCKET_POLICY) self.assert_fails(plan, "dev", BUCKET_POLICY)
def test_deploy_policy_rejects_resource_action_and_extra_statement(self) -> None: def test_github_deploy_policy_stays_byte_identical(self) -> None:
for mutation in ("resource", "action", "extra"): source = (
plan = make_plan( REPOSITORY / "terraform/live/modules/environment-owned/main.tf"
"staging", ).read_text(encoding="utf-8")
mode="controlled", document = source.split('data "aws_iam_policy_document" "github_deploy" {', 1)[1]
controlled_updates={DEPLOY_POLICY}, document = document.split("resource ", 1)[0]
) self.assertNotIn("var.adoption_complete", document)
policy = copy.deepcopy(deploy_policy("staging")) self.assertIn("AssumeCdkBootstrapRoles", document)
if mutation == "resource": self.assertIn("DescribeStack", document)
policy["Statement"][0]["Resource"] = "*" self.assertNotIn("ReadDeploymentBucket", document)
elif mutation == "action": self.assertNotIn("PublishAndRollbackSiteObjects", document)
policy["Statement"][0]["Action"].append("iam:PassRole") self.assertNotIn(
else: "module.environment_owned.aws_iam_role_policy.github_deploy",
policy["Statement"].append( CONTROLLED_UPDATE_ADDRESSES,
{ )
"Sid": "Extra",
"Effect": "Allow", def test_deploy_policy_is_not_eligible_for_controlled_update(self) -> None:
"Action": "s3:*", plan = make_plan("dev", mode="controlled", controlled_updates=set())
"Resource": "*", self.assert_fails(plan, "dev", DEPLOY_POLICY)
} plan = make_plan("dev", mode="controlled", controlled_updates=set())
) resource(plan, DEPLOY_POLICY)["change"] = {
resource(plan, DEPLOY_POLICY)["change"]["after"]["policy"] = json.dumps( "actions": ["update"],
policy "before": {"policy": "{}"},
) "after": {"policy": '{"Version":"2012-10-17"}'},
with self.subTest(mutation=mutation): }
self.assert_fails(plan, "staging", DEPLOY_POLICY) self.assert_fails(plan, "dev", DEPLOY_POLICY)
def test_policy_updates_require_exact_pre_adoption_state(self) -> None: def test_policy_updates_require_exact_pre_adoption_state(self) -> None:
for environment in REQUIRED_RESOURCES: for environment in REQUIRED_RESOURCES:
for address in (BUCKET_POLICY, DEPLOY_POLICY): plan = make_plan(
plan = make_plan( environment,
environment, mode="controlled",
mode="controlled", controlled_updates={BUCKET_POLICY},
controlled_updates={address}, )
) change = resource(plan, BUCKET_POLICY)["change"]
change = resource(plan, address)["change"] before = json.loads(change["before"]["policy"])
before = json.loads(change["before"]["policy"]) before["Statement"].append(
before["Statement"].append( {
{ "Sid": "UnexpectedDrift",
"Sid": "UnexpectedDrift", "Effect": "Deny",
"Effect": "Deny", "Action": "*",
"Action": "*", "Resource": "*",
"Resource": "*", }
} )
) change["before"]["policy"] = json.dumps(before)
change["before"]["policy"] = json.dumps(before) with self.subTest(environment=environment):
with self.subTest(environment=environment, address=address): self.assert_fails(plan, environment, BUCKET_POLICY)
self.assert_fails(plan, environment, address)
def test_controlled_update_rejects_unknown_and_replace_paths(self) -> None: def test_controlled_update_rejects_unknown_and_replace_paths(self) -> None:
for field, value in ( for field, value in (

View file

@ -4,11 +4,11 @@ This tree adopts the existing Sea Haven SHOC frontend dev hosting resources
into HCP Terraform without recreating them. It mirrors the backend adoption into HCP Terraform without recreating them. It mirrors the backend adoption
(`shoc-backend` #94, #98, #99, #102) and lands in three PRs: (`shoc-backend` #94, #98, #99, #102) and lands in three PRs:
| PR | Branch | Change | | PR | Branch | Change |
| --- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | --- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| A | `feature/frontend-terraform-adoption` | This PR. Dev root with `adoption_complete = false`, import guard, CDK retain mode, push-to-`dev` deploy off. | | A | `feature/frontend-terraform-adoption` | Merged. Dev root with `adoption_complete = false`, import guard, CDK retain mode, push-to-`dev` deploy off. |
| B | `feature/terraform-dev-adoption` | `adoption_complete = true`: ownership tags, bucket policy drops the auto-delete grant, CloudFormation detaches. | | B | `feature/terraform-dev-adoption` | This PR. `adoption_complete = true`: ownership tags, bucket policy drops the auto-delete grant. |
| C | `feature/terraform-dev-content-cd` | Content CD through Terraform: release prefixes, pointer object, origin group, invalidation action, rollback. | | C | `feature/terraform-dev-content-cd` | Content CD through Terraform: release prefixes, pointer object, origin group, invalidation action, rollback. |
Creating these files, formatting them, initializing with `-backend=false`, and Creating these files, formatting them, initializing with `-backend=false`, and
validating them does not authorize an AWS, HCP Terraform, GitHub, validating them does not authorize an AWS, HCP Terraform, GitHub,
@ -45,9 +45,8 @@ before the first release after any Terraform merge:
`>= 1.9.0, < 2.0.0`; CI validates with `1.16.0`). `>= 1.9.0, < 2.0.0`; CI validates with `1.16.0`).
- Dynamic AWS credentials only: environment variables - Dynamic AWS credentials only: environment variables
`TFC_AWS_PROVIDER_AUTH=true`, `TFC_AWS_PLAN_ROLE_ARN`, and `TFC_AWS_PROVIDER_AUTH=true`, `TFC_AWS_PLAN_ROLE_ARN`, and
`TFC_AWS_APPLY_ROLE_ARN` pointing at the `seahaven-org-baseline` roles `TFC_AWS_APPLY_ROLE_ARN` pointing at `hcptf-shoc-frontend-new-dev-plan`
`hcptf-shoc-frontend-new-dev-plan` and `hcptf-shoc-frontend-new-dev`. No and `hcptf-shoc-frontend-new-dev`. No access keys.
access keys.
- **No** `adoption_complete` workspace variable. The dev root pins it in code - **No** `adoption_complete` workspace variable. The dev root pins it in code
(`local.adoption_complete`) so the value under review is the value that (`local.adoption_complete`) so the value under review is the value that
applies. `scripts/test-terraform-import-plan-check.py` fails if a `variable` applies. `scripts/test-terraform-import-plan-check.py` fails if a `variable`
@ -86,7 +85,6 @@ The following remain outside state:
- `CDKToolkit` resources and CDK metadata - `CDKToolkit` resources and CDK metadata
- the S3 auto-delete custom resource, its provider Lambda and role - the S3 auto-delete custom resource, its provider Lambda and role
- the HCP plan/apply roles and the deploy-role permissions boundary - the HCP plan/apply roles and the deploy-role permissions boundary
(`seahaven-org-baseline` owns them)
## Exact live inventory (dev) ## Exact live inventory (dev)
@ -130,7 +128,7 @@ If read-back after that deploy differs from the root in any other way, update
the root to the observed value and prove a zero-change import plan. Do not the root to the observed value and prove a zero-change import plan. Do not
approve drift through the controlled-update checker. approve drift through the controlled-update checker.
## Phase 1: import-first adoption (this PR) ## Phase 1: import-first adoption (merged)
Each step is gated. State the impact, get the go, act, read back, record. Each step is gated. State the impact, get the go, act, read back, record.
@ -177,7 +175,7 @@ Each step is gated. State the impact, get the go, act, read back, record.
After Phase 1 CloudFormation still owns every resource. Terraform holds state After Phase 1 CloudFormation still owns every resource. Terraform holds state
for them and nothing else. for them and nothing else.
## Phase 2: controlled ownership transfer (PR B) ## Phase 2: controlled ownership transfer (this PR)
PR B pins `adoption_complete = true`. The controlled apply may update only: PR B pins `adoption_complete = true`. The controlled apply may update only:
@ -189,15 +187,19 @@ PR B pins `adoption_complete = true`. The controlled apply may update only:
- `module.environment_owned.aws_iam_role.github_deploy` (tags) - `module.environment_owned.aws_iam_role.github_deploy` (tags)
The OAC, both Route 53 records, and the deploy inline policy must be no-op. The OAC, both Route 53 records, and the deploy inline policy must be no-op.
PR B keeps the post-adoption inline policy byte-identical to live so the PR B keeps the GitHub deploy inline policy byte-identical to live so
policy address does not appear in the plan. Run the checker with one `aws_iam_role_policy.github_deploy` does not appear in the plan. Run the
`--allow-update-address` per updating address; it rejects unused allowlist checker with one `--allow-update-address` per updating address; it rejects
entries, unknown values, and replacements. unused allowlist entries, unknown values, and replacements:
Dependency: `hcptf-shoc-frontend-new-dev` currently lacks ```bash
`cloudfront:UpdateDistribution` and `cloudfront:UpdateFunction`. Codify the python3 scripts/check-terraform-import-plan.py plan.json --environment dev \
expansion in `seahaven-org-baseline` (cross-family plus security review) and --allow-update-address module.environment_owned.aws_s3_bucket.site \
deploy it before the controlled apply. --allow-update-address module.environment_owned.aws_s3_bucket_policy.site \
--allow-update-address module.environment_owned.aws_cloudfront_distribution.site \
--allow-update-address module.environment_owned.aws_cloudfront_function.spa_rewrite \
--allow-update-address module.environment_owned.aws_iam_role.github_deploy
```
After the apply and a no-op plan, deploy the same reviewed CDK SHA with After the apply and a no-op plan, deploy the same reviewed CDK SHA with
`--parameters ManageSiteInfrastructure=false`. Expect `DELETE_SKIPPED` on the `--parameters ManageSiteInfrastructure=false`. Expect `DELETE_SKIPPED` on the
@ -205,6 +207,9 @@ After the apply and a no-op plan, deploy the same reviewed CDK SHA with
`ManageSiteInfrastructure=true` again after that. See `ManageSiteInfrastructure=true` again after that. See
[`infra/cdk/README.md`](../infra/cdk/README.md). [`infra/cdk/README.md`](../infra/cdk/README.md).
Confirm `dev.seahaven.com` still serves and that a manual `workflow_dispatch`
of `deploy.yml` can still upload with the unchanged GitHub content policy.
## Phase 3: content CD through Terraform (PR C) ## Phase 3: content CD through Terraform (PR C)
Summary only; PR C carries the full design. GitHub builds and uploads to an Summary only; PR C carries the full design. GitHub builds and uploads to an

View file

@ -1,7 +1,6 @@
locals { locals {
# Import-first phase. Pinned in code, never a workspace variable: the # Controlled ownership transfer. Pinned in code, never a workspace variable.
# controlled ownership transfer flips this to true in its own reviewed PR. adoption_complete = true
adoption_complete = false
environment = "dev" environment = "dev"
workspace_name = "shoc-frontend-new-dev" workspace_name = "shoc-frontend-new-dev"

View file

@ -109,8 +109,11 @@ data "aws_iam_policy_document" "github_deploy_assume" {
} }
data "aws_iam_policy_document" "github_deploy" { data "aws_iam_policy_document" "github_deploy" {
# Byte-identical to the live GitHub content policy through Phase 2 so
# aws_iam_role_policy.github_deploy stays no-op. Phase 3 replaces this
# with the release-prefix policy.
dynamic "statement" { dynamic "statement" {
for_each = !var.adoption_complete && var.environment == "dev" ? [1] : [] for_each = var.environment == "dev" ? [1] : []
content { content {
sid = "AssumeCdkBootstrapRoles" sid = "AssumeCdkBootstrapRoles"
@ -120,72 +123,31 @@ data "aws_iam_policy_document" "github_deploy" {
} }
} }
dynamic "statement" { statement {
for_each = var.adoption_complete ? [] : [1] sid = "DescribeStack"
effect = "Allow"
content { actions = ["cloudformation:DescribeStacks"]
sid = "DescribeStack" resources = ["arn:aws:cloudformation:${var.aws_region}:${var.aws_account_id}:stack/${var.cloudformation_stack_name}/*"]
effect = "Allow"
actions = ["cloudformation:DescribeStacks"]
resources = ["arn:aws:cloudformation:${var.aws_region}:${var.aws_account_id}:stack/${var.cloudformation_stack_name}/*"]
}
} }
dynamic "statement" { statement {
for_each = var.adoption_complete ? [] : [1] effect = "Allow"
actions = [
content { "s3:Abort*",
effect = "Allow" "s3:DeleteObject*",
actions = [ "s3:GetBucket*",
"s3:Abort*", "s3:GetObject*",
"s3:DeleteObject*", "s3:List*",
"s3:GetBucket*", "s3:PutObject",
"s3:GetObject*", "s3:PutObjectLegalHold",
"s3:List*", "s3:PutObjectRetention",
"s3:PutObject", "s3:PutObjectTagging",
"s3:PutObjectLegalHold", "s3:PutObjectVersionTagging",
"s3:PutObjectRetention", ]
"s3:PutObjectTagging", resources = [
"s3:PutObjectVersionTagging", local.bucket_arn,
] "${local.bucket_arn}/*",
resources = [ ]
local.bucket_arn,
"${local.bucket_arn}/*",
]
}
}
dynamic "statement" {
for_each = var.adoption_complete ? [1] : []
content {
sid = "ReadDeploymentBucket"
effect = "Allow"
actions = [
"s3:GetBucketLocation",
"s3:GetBucketVersioning",
"s3:ListBucket",
"s3:ListBucketVersions",
]
resources = [local.bucket_arn]
}
}
dynamic "statement" {
for_each = var.adoption_complete ? [1] : []
content {
sid = "PublishAndRollbackSiteObjects"
effect = "Allow"
actions = [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:GetObject",
"s3:GetObjectVersion",
"s3:PutObject",
]
resources = ["${local.bucket_arn}/*"]
}
} }
statement { statement {

View file

@ -10,7 +10,7 @@ variable "environment" {
variable "adoption_complete" { variable "adoption_complete" {
type = bool type = bool
description = "Switches only ownership tags and the deploy policy to their adopted values." description = "Switches ownership tags and drops the auto-delete helper grant from the bucket policy. The GitHub deploy inline policy stays byte-identical to live until the content-CD PR."
default = false default = false
} }