Skip to content

Fix DeleteAccessEntry denial in the agentless enrollment policy - #6

Open
BenEdgar17 wants to merge 2 commits into
mainfrom
feature/fix-agentless-delete-permission
Open

BenEdgar17 wants to merge 2 commits into
mainfrom
feature/fix-agentless-delete-permission

Conversation

@BenEdgar17

@BenEdgar17 BenEdgar17 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Splits the agentless enrollment policy's combined Create/Delete/Describe access-entry statement in all three aws-generic CloudFormation templates:

  • eks:CreateAccessEntry keeps the eks:principalArn StringEquals condition (documented and proven to work for Create) and is now scoped to the cluster ARN it actually authorizes against.
  • eks:DeleteAccessEntry / eks:DescribeAccessEntry drop the condition and are instead resource-scoped to the agentless role's access-entry ARN pattern (…:access-entry/*/role/<acct>/Dalton-EKS-Agentless-<acct>/*) — the same pattern the Associate/Disassociate statement already uses in production.

Root cause (corrected after CloudTrail analysis)

The eks:principalArn condition on DeleteAccessEntry only matches when the access entry actually exists. CloudTrail from the dev account shows the same policy, same role generation, same request:

  • Delete of an existing entry → allowed (2026-07-26 10:19Z).
  • Delete when no entry existsAccessDeniedException instead of ResourceNotFoundException (10:56Z, 13:49Z, 14:27Z — and at 14:27:06Z the same second's CreateAccessEntry succeeded, proving the policy itself was live and healthy).

Dalton's cleanup and recreate-on-enroll paths deliberately rely on a tolerable not-found (delete-then-create rebind per ENG-4311; best-effort cleanup per ENG-4310). Under the conditioned statement, every no-entry delete surfaces as a scary AccessDenied "failed" outcome in the deletion report and defeats the ENG-4311 stale-entry rebind. Resource-scoping makes delete authorization independent of entry existence: existing entries delete cleanly, absent entries return ResourceNotFound, which the backend already tolerates.

Rollout

  1. Merge, then sync the three templates to s3://cf-templates-w9lau7h9v2y20-eu-north-1/aws-generic/.
  2. Existing stacks do not pick this up automatically — CloudFormation caches templates. Each installed stack needs an update-stack with the template URL re-specified (including the dev test stack dalton-aws-integration in eu-north-1).

Linear

Part of ENG-4257

🤖 Generated with Claude Code

BenEdgar17 and others added 2 commits July 26, 2026 16:56
…lment policy

DeleteAccessEntry/DescribeAccessEntry are authorized against the
access-entry resource ARN and the eks:principalArn condition key is not
populated for them, so the shared statement's StringEquals silently denied
deletion (hit live: datasource-deletion cleanup got AccessDeniedException
while creation succeeded). Create keeps the principalArn condition (proven
live) and is now scoped to cluster ARNs; Delete/Describe are scoped by the
agentless role's access-entry ARN — the same pattern the Associate
statement already uses, also proven live.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CloudTrail analysis showed the eks:principalArn condition DOES match
DeleteAccessEntry when the entry exists; every observed denial was on a
nonexistent entry (AccessDenied masking what should be ResourceNotFound).
The fix is unchanged — resource-scoping makes authorization independent
of entry existence — but the comment now records the true mechanism.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BenEdgar17

Copy link
Copy Markdown
Contributor Author

Correction to the original description: the first version of this PR claimed eks:principalArn is never populated for DeleteAccessEntry. CloudTrail evidence disproved that — full trail of every DeleteAccessEntry on the dev cluster:

Time (UTC) Caller Entry existed? Result
Jul 25 20:50 ben (admin) yes success (manual orphan cleanup)
Jul 26 10:19 viewer role gen 2, fresh session yes success — conditioned policy allowed it
Jul 26 10:56 viewer role gen 3, fresh session no AccessDenied
Jul 26 13:49 viewer role gen 3 (~3h old, propagation ruled out) no AccessDenied
Jul 26 13:56 ben (admin) no ResourceNotFoundException
Jul 26 14:27:06 viewer role gen 4 no AccessDenied — while CreateAccessEntry in the same second, same role succeeded

All four role generations carried byte-identical policy documents (verified via PutRolePolicy events), all denials used fresh STS sessions from the then-live role, and no extra policies were ever attached. The only discriminator is entry existence: the condition key matches an existing entry and is unmatchable when the entry is absent, so the caller gets AccessDenied where ResourceNotFound belongs.

The diff is unchanged and still the right fix — resource-scoping decouples delete authorization from entry existence. Commit 6f2a16c updates the in-template comment to record the true mechanism.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant