- Newest
- Most votes
- Most comments
The decoded message settles question 2
The resource in the failed authorization is arn:aws:ec2:us-east-2:<ACCOUNT_ID>:*/* — wildcard resource type, not a specific ENI. Combined with the context keys present (aws:Region, aws:ID, aws:Service, aws:Resource, aws:Type, aws:Account, aws:ARN) and the absence of ec2:Subnet / ec2:Vpc, this is a permission probe against no resource at all.
That has two consequences your policy can't escape:
Your Resource never matches. network-interface/* cannot match a request resource of */*. Hence matchedStatements: [] with explicitDeny: false — implicit deny because nothing matched, not because a condition failed.
Your conditions cannot be satisfied. ec2:Subnet and ec2:Vpc are absent from the request context. A StringEquals on an absent key evaluates false. No value you could write makes it true.
So the answer to question 1 is no — not as a property of CodeBuild's implementation, but as a property of IAM evaluation. A single Allow statement cannot both match a resource-agnostic probe and constrain on resource-derived keys that the probe doesn't carry. There's no supported policy shape that does this, and none could exist.
Historically this check ran with DryRun, which returns DryRunOperation on success and UnauthorizedOperation on failure — matching the error string you're seeing. Reports from 2023 describe the same failure appearing after the call sequence changed. Your null requestParameters is consistent with either path; the decoded message is the stronger evidence either way, and it points the same direction.
What actually achieves your containment goal
You've correctly identified that scoping CreateNetworkInterfacePermission doesn't contain a separate broad DeleteNetworkInterface Allow. But you've been trying to solve this on the Allow, and the Allow is the wrong instrument.
Split the two request shapes with an explicit Deny:
{ "Effect": "Allow", "Action": "ec2:DeleteNetworkInterface", "Resource": "*", "Condition": { "StringEquals": { "aws:RequestedRegion": "us-east-2" } } }, { "Effect": "Deny", "Action": "ec2:DeleteNetworkInterface", "Resource": "*", "Condition": { "StringNotEquals": { "ec2:Subnet": [ "arn:aws:ec2:us-east-2:<ACCOUNT_ID>:subnet/subnet-aaa", "arn:aws:ec2:us-east-2:<ACCOUNT_ID>:subnet/subnet-bbb" ] } } }
The asymmetry you need is already in the request context. The probe carries no ec2:Subnet, so StringNotEquals doesn't match and the Deny is inert — the probe passes. A real deletion targets an existing ENI, which resolves to a subnet, so the Deny always evaluates and blocks anything outside your list.
This is materially different from IfExists on the Allow, which you rejected for good reason: IfExists grants on missing context. Here the Deny is the containment mechanism, and it is unconditional for any request that carries resource context.
The residual risk, stated plainly: containment holds only if EC2 populates ec2:Subnet for every real DeleteNetworkInterface. The service authorization reference lists the key as supported for that action — which you already found — but "supported" is not the same as "guaranteed populated on every code path." I can't prove the stronger claim, and you shouldn't accept it from me. That's a reasonable thing to ask AWS to confirm in writing, and it's a narrower, more answerable question than the three you posed.
If you want the assertion tested rather than assumed: create an ENI outside the permitted subnets, attempt deletion with the build role credentials, and confirm you get an explicit deny rather than success. That gives you evidence rather than documentation.
Architectural alternatives worth weighing
If the requirement is strict enough that residual risk is unacceptable:
Remove the grant entirely. Detach the project from the VPC and reach private resources by other means — VPC endpoints exposed to CodeBuild's managed network, or a bastion/proxy pattern. No VPC config means no ENI lifecycle and no delete permission on the role.
Isolate the account. If a broad delete grant is unavoidable, a dedicated build account contains blast radius by construction rather than by condition-key behaviour. You raised this yourself in question 3, and I'd say it's the sound answer independent of whatever AWS confirms — it doesn't depend on any assumption about context-key population.
Check CodeBuild fleets. Reserved-capacity fleets manage ENI lifecycle differently from on-demand builds. Whether that changes the permission model for your case is worth verifying, but it's a plausible way out of the problem shape.
Relevant content
- AWS OFFICIALUpdated 10 months ago
