fix(deployer-iam): split EFS mount targets out of tag-conditioned statement; add ServiceLinkedRoles#2
fix(deployer-iam): split EFS mount targets out of tag-conditioned statement; add ServiceLinkedRoles#2EnriqueCanals wants to merge 2 commits into
Conversation
…tement; add ServiceLinkedRoles
Two CloudFormation deployment failures caused by deployer IAM policy gaps:
Fix 1 — EFS mount targets can't be tag-conditioned:
The single EFSManage statement bundled file-system + mount-target actions
under aws:ResourceTag/Name=bclaw-data. Mount targets are untaggable, so the
condition always evaluated to false and implicitly denied CreateMountTarget
(403). Split into EFSManageTagged (tag-conditioned: DeleteFileSystem,
Create/DeleteAccessPoint, CreateTags, TagResource) and EFSMountTargets
(unconditional Resource:*: Create/Delete/DescribeMountTargets).
Fix 2 — Missing iam:CreateServiceLinkedRole:
When a service-linked role doesn't exist, AWS auto-creates it using the
caller's credentials. The deployer had no iam:CreateServiceLinkedRole at all,
so ECS service creation failed with 400 ("Unable to assume the service
linked role"). Added a ServiceLinkedRoles statement scoped via
iam:AWSServiceName to three SLRs: AWSServiceRoleForAmazonElasticFileSystem
(EFS), AWSServiceRoleForECS (ECS), AWSServiceRoleForApplicationAutoScaling_ECSService
(autoscaling). Note the naming inconsistency: ECS SLR is AWSServiceRoleForECS
(no "Amazon" prefix), EFS SLR is AWSServiceRoleForAmazonElasticFileSystem (with).
Docs: README policy tables, setup-bclaw SKILL prerequisites (factual, no
narrative per AGENTS.md skill rules), and references/iam-and-template-lessons.md
(full discovery narrative + diagnostic approach — author-only, not shipped).
Test: test/policy.test.mjs (TDD — written RED first, 6 assertions guarding
the split, the unconditional mount-target statement, and the SLR scope/naming).
| "Sid": "EFSMountTargets", | ||
| "Effect": "Allow", | ||
| "Action": [ | ||
| "elasticfilesystem:CreateMountTarget", "elasticfilesystem:DeleteMountTarget", |
There was a problem hiding this comment.
confirm we actually need elasticfilesystem:DeleteMountTarget for teardown (this may get cleaned up automatically when the stack gets torn down)
There was a problem hiding this comment.
Good catch. Mount targets are CloudFormation-owned resources (AWS::EFS::MountTarget, default DeletionPolicy: Delete), so CloudFormation's stack-delete handler calls DeleteMountTarget using the deployer's credentials. The deployer never calls it directly — it's only consumed by the CFN handler.
Without the permission, every stack delete would 403 on the mount targets and force the FORCE_DELETE_STACK recovery path. The end state would be the same (Phase 3's delete-file-system auto-cleans any surviving mount targets), but the normal clean-delete path would be broken on every teardown.
That said, the latest commit (97353af) folds DeleteMountTarget back into the tag-conditioned EFSManage statement rather than leaving it unconditional. The IAM definition confirms mount-target actions evaluate against the file-system resource type (which supports aws:ResourceTag), so the tag condition scopes it to the claw's own file system. The original 403 may have been from the missing iam:CreateServiceLinkedRole (the other fix on this branch), not the tag condition. A live deploy is the right place to confirm, so I'll give that another test with a fresh bclaw install.
…ioned EFSManage The AWS Service Authorization Reference (checked via policy_sentry's IAM definition database) confirms there is NO mount-target resource type in EFS IAM. All three mount-target actions (CreateMountTarget, DeleteMountTarget, DescribeMountTargets) evaluate against the file-system resource type, which DOES support aws:ResourceTag. The EFS file system IS tagged Name=bclaw-data, so the tag condition scopes mount-target actions correctly -- the original single EFSManage statement was right. The previous commit split EFSManage into EFSManageTagged (tag-conditioned) + EFSMountTargets (unconditional Resource: *) based on the assumption that mount targets are untaggable. That diagnosis was likely wrong -- the real deploy failure was probably caused by the missing iam:CreateServiceLinkedRole (also fixed in this branch), not the tag condition. Changes: - Revert to single EFSManage statement with all EFS manage/mutate actions under aws:ResourceTag/Name = bclaw-data - Remove the EFSMountTargets statement (tighter: no unconditional Resource: *) - Update tests to assert the unified structure - Update README, setup/teardown skills, and references docs - Add tmp/ to .gitignore (scratch dir from previous session) Not verified with a real deploy -- see references/iam-and-template-lessons.md for the caveat. Needs a live deploy before merging.
Two CloudFormation deployment failures caused by deployer IAM policy gaps:
Fix 1 — EFS mount targets can't be tag-conditioned: The single EFSManage statement bundled file-system + mount-target actions under aws:ResourceTag/Name=bclaw-data. Mount targets are untaggable, so the condition always evaluated to false and implicitly denied CreateMountTarget (403). Split into EFSManageTagged (tag-conditioned: DeleteFileSystem, Create/DeleteAccessPoint, CreateTags, TagResource) and EFSMountTargets (unconditional Resource:*: Create/Delete/DescribeMountTargets).
Fix 2 — Missing iam:CreateServiceLinkedRole:
When a service-linked role doesn't exist, AWS auto-creates it using the caller's credentials. The deployer had no iam:CreateServiceLinkedRole at all, so ECS service creation failed with 400 ("Unable to assume the service linked role"). Added a ServiceLinkedRoles statement scoped via iam:AWSServiceName to three SLRs: AWSServiceRoleForAmazonElasticFileSystem (EFS), AWSServiceRoleForECS (ECS), AWSServiceRoleForApplicationAutoScaling_ECSService (autoscaling). Note the naming inconsistency: ECS SLR is AWSServiceRoleForECS (no "Amazon" prefix), EFS SLR is AWSServiceRoleForAmazonElasticFileSystem (with).
Docs: README policy tables, setup-bclaw SKILL prerequisites (factual, no narrative per AGENTS.md skill rules), and references/iam-and-template-lessons.md (full discovery narrative + diagnostic approach — author-only, not shipped).
Test: test/policy.test.mjs (TDD — written RED first, 6 assertions guarding the split, the unconditional mount-target statement, and the SLR scope/naming).