Skip to content

Build1 publisher3 min readPublished

CloudFormation's ALB provider calls an EC2 lookup that a template-derived role denies

A dev.to account of two AccessDenied failures shows CloudFormation resource providers making supporting calls into adjacent services, so an execution role scoped to the template's service namespaces is incomplete by construction.

The Engineer · Build desk

Illustration accompanying CloudFormation's ALB provider calls an EC2 lookup that a template-derived role denies

What happened

  • A CloudFormation execution role scoped to a template's resources can still fail mid-deployment, because resource implementations make supporting lookups, associations, tagging and cleanup calls into adjacent AWS services.
  • One observed Application Load Balancer creation path required ec2:GetSecurityGroupsForVpc, an action in the EC2 namespace for a resource owned by Elastic Load Balancing.
  • The fix published for the EC2 case grants the single action on Resource "*", bounded by a StringEquals condition on aws:RequestedRegion.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint A tool that builds a role by reading resource types can only produce a lower bound, and the missing actions show up as a failed deployment in whichever environment runs first.
  • decision Each denial puts a choice in front of whoever is on the deploy: grant the one action the error named, or attach the adjacent service's managed policy and leave the role wide.
  • exposure Update and delete need their own proof even after create passes, so a stack tested only on the forward path can strand a rollback behind an AccessDenied.
  • precedent Because AWS changed the Web ACL association path, permission sets pinned to an old denial go stale, and discovered actions need revalidating whenever an association model changes.

The unit of authorization is the IAM action. The unit of a template is the resource type. The two line up only loosely: a resource implementation can perform supporting lookups, associations, tagging, or cleanup through APIs in adjacent AWS services [2]. A role assembled from the service namespaces visible in a template covers a subset of the actions the deployment will need [22].

In the load balancer case the provider called the current EC2 API GetSecurityGroupsForVpc [4]. Two properties make that hard to anticipate. The action lives in the ec2 namespace while the resource is an Elastic Load Balancing resource, and it is not a `Describe*` call [5], so the habitual read-only wildcard `ec2:Describe*` does not match it [20].

The correction in the post is a single statement: Allow on `ec2:GetSecurityGroupsForVpc`, `Resource: "*"`, with a StringEquals condition on `aws:RequestedRegion` [9]. The star is the part a reviewer will circle, and it is the part that is defensible here, because some AWS APIs do not offer useful resource-level ARN scoping for the request [10]. Least privilege then comes from the smallest action set plus the condition keys the API actually supports [11].

The reflex after an unexpected denial is to attach a broad managed policy for the adjacent service until the deployment succeeds [12]. The post's objection is that this resolves uncertainty by expanding privilege and discards the most useful evidence in the failure, since AWS already reported the exact denied action [13]. Neither observed case needed the wider grant: broad EC2 read or write access was unnecessary for the lookup, and broad load-balancer access was unnecessary for the association [14].

The Web ACL example is the reason to keep revisiting the answer. The observed denial involved `elasticloadbalancing:SetWebACL` [7]. Current AWS WAF documentation distinguishes that older Application Load Balancer setting from a newer association model using load-balancer-side `CreateWebACLAssociation` and `DeleteWebACLAssociation` alongside WAF permissions [8]. A permission list copied from that incident would carry SetWebACL and be missing both association actions [21].

So the post treats the denial as dependency discovery rather than a policy bug, and gives a sequence:

1. Capture the exact denied action and resource, if one is reported. 2. Identify the CloudFormation resource or association operation active at the time. 3. Check current AWS documentation for that provider or integration path. 4. Determine whether the missing API supports resource-level scoping. 5. Add only the required action with the strongest supported resource or condition boundary. 6. Review a fresh change set before protected execution. 7. Add the newly discovered dependency to a static policy-contract test [15].

The verification standard is that the same resource operation succeeds under the narrowed execution role without a broader managed policy [16], and for a provider-side dependency it has to cover the forward operation plus any corresponding update or removal path [17].

The post advises against preserving an action forever because one historical provider path used it, and recommends revalidating integration permissions when AWS changes the underlying association model [18]. It reports two observed examples of the class [19], and it does not say how many resource types make cross-service calls.

What to watch

  • Whether AWS documents the cross-service calls each CloudFormation resource type makes. That is what a policy generator would read.
  • Whether the newer WAF association model pulls additional load-balancer actions on the delete path as well as the create path.
  • Whether static policy-contract tests catch the next provider-side dependency before a deployment fails, or only record the ones already found.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories