Skip to content

Can CodeBuild's generic DeleteNetworkInterface precheck coexist with strict subnet-scoped deletion?

0

An on-demand Linux CodeBuild project attached to a private VPC subnet fails in QUEUED with:

UNAUTHORIZED_OPERATION_DELETE_NETWORK_INTERFACE

The service role contains an Allow for ec2:DeleteNetworkInterface on:

arn:aws:ec2:us-east-2:<ACCOUNT_ID>:network-interface/*

It requires aws:RequestedRegion = us-east-2 and exact ec2:Subnet / ec2:Vpc ARN conditions.

The matching CloudTrail event uses the intended service role and region. Its decoded authorization message reports:

{
  "allowed": false,
  "explicitDeny": false,
  "matchedStatements": { "items": [] },
  "failures": { "items": [] },
  "context": {
    "action": "DeleteNetworkInterface",
    "resource": "arn:aws:ec2:us-east-2:<ACCOUNT_ID>:*/*"
  }
}

The returned condition list includes aws:Region, aws:ID, aws:Service, aws:Resource, aws:Type, aws:Account, and aws:ARN; it does not include ec2:Subnet or ec2:Vpc. CloudTrail requestParameters is null, so we cannot establish whether DryRun or another internal validation mechanism was used.

AWS's CodeBuild VPC policy example grants ENI deletion on Resource:* and separately restricts CreateNetworkInterfacePermission by subnet and authorized service. The EC2 authorization reference lists subnet/VPC condition support for DeleteNetworkInterface.

Our requirement is to prevent real deletion of unrelated ENIs, including direct EC2 calls made using the build role credentials. Restricting CreateNetworkInterfacePermission alone does not establish that containment for a separate DeleteNetworkInterface Allow.

  1. Is there an officially supported policy that passes this generic CodeBuild check while enforcing fail-closed subnet/VPC restrictions for real deletion?
  2. Is there a documented distinction between the service precheck and actual deletion that IAM can enforce, or a separate service authorization path?
  3. If no such same-account arrangement is supported, can AWS confirm that account-level isolation is the appropriate boundary when a broader delete grant is necessary?

Please cite the applicable documentation or service-team guidance. We are not looking for an IfExists workaround that permits missing-context requests without proving containment, or a claim that broad deletion authority only applies to the precheck.

The failed test setup has been torn down. Account IDs, resource IDs, credentials, encoded authorization tokens and raw logs are intentionally omitted. This is a request for guidance, not a report of an exploitable service vulnerability.

1 Answer
0

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.

EXPERT

answered a month ago

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.