Repository navigation
fix(server): name the supplied ARN in a denial - #509
Open
LeeroyHannigan wants to merge 2 commits into
Open
LeeroyHannigan wants to merge 2 commits into
LeeroyHannigan wants to merge 2 commits into
Conversation
…ount's resource The authorization layer canonicalizes every supplied resource ARN to this deployment's partition, service, region and account so a Deny on the real resource matches however the caller spelled it. The denial message then named that canonical ARN. DynamoDB names the ARN as the caller supplied it, partition, service and region included, and answers a resource in another account with AccessDeniedException "Access is denied" for every principal (measured 2026-10-20 with TagResource, admin and no-policy callers). Each authorization target now carries both spellings: the canonical ARN the policies are matched against and the ARN as written, which the denial names. A tag operation whose ResourceArn names a table or stream in another account is refused with "Access is denied" before the IAM decision, the answer the engine already gave an authorized caller. Fixes #447
…ble ARNs Tag operations now refuse a resource ARN in another account before the shape check. The service answers "Access is denied" for another account's index ARN and backup ARN on TagResource, where the same ARN in this account gets the shape ValidationException; the branch answered the ValidationException for both. A denial names the field as written only when it parses as a DynamoDB resource ARN. An index Query or Scan whose TableName is an index ARN or a malformed resource named that string with "/index/<name>" appended, a resource the caller never sent; it now names the canonical index ARN.
LeeroyHannigan
requested review from
amrith,
c33howard,
jcshepherd,
pdf-amzn,
robinnsc and
yesyayen
as code owners
October 24, 2026 14:20
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A caller with no policy who names a resource by an ARN in another partition, service or region gets an
AccessDeniedExceptionwhose resource is the ARN rewritten to this deployment. DynamoDB names the ARN as the caller wrote it. An ARN in another account gets the long denial here; DynamoDB answersAccess is deniedto an admin and to a no-policy caller alike. This change keeps the caller's spelling for the message and refuses another account's resource before the IAM decision.Fixes #447
What changed
crates/server/src/authz_resource.rs:authz_targetsreturnsAuthzTarget { action, resource, supplied }.resourceis the canonical ARN for this deployment and is what the policies are matched against, as before.suppliedis what a denial names. It is the request field as written when the field parses as a DynamoDB resource ARN (ResourceArn,StreamArn, the exportTableArn,SourceTableArn, a table ARN inTableName, aRequestItemskey or aTransactItemstable; aBackupArnis trimmed first, as the engine trims it). Otherwise it is the canonical ARN: for a bare name, and for an index Query or Scan whoseTableNameis not a table ARN.check_resource_accountrefuses atable/...ARN whose account is not the caller's withAccessDeniedException"Access is denied".crates/server/src/authorization.rs:check_authorizationtakes anAuthzTarget; the policy evaluation usestarget.resourceanddeny_messagenamestarget.supplied.crates/server/src/request_helpers.rs: the tag operations runcheck_resource_accountfirst, then the existingResourceArnshape check, both before the IAM decision; everycheck_authorizationcall site builds a target.crates/server/src/authz_resource/supplied_resource_tests.rs: unit tests for both spellings on each ARN-bearing field, the index Query rule and the account check.Service measurements
Live DynamoDB, account
<scratch-acct>, us-east-1, tablebb-arn-main. The TagResource rows were measured three times on 2026-10-20 and 2026-10-21, with identical answers.arn:aws-cn:dynamodb:us-east-1:<acct>:table/bb-arn-mainInvalid TableArn: Invalid ResourceArn provided as input <arn>User: <principal> is not authorized to perform: dynamodb:TagResource on resource: arn:aws-cn:dynamodb:us-east-1:<acct>:table/bb-arn-main because no identity-based policy allows the dynamodb:TagResource actionarn:aws:s3:us-east-1:<acct>:table/bb-arn-mainarn:aws:s3:us-east-1:<acct>:table/bb-arn-mainarn:aws:dynamodb:us-west-2:<acct>:table/bb-arn-mainarn:aws:dynamodb:us-west-2:<acct>:table/bb-arn-mainarn:aws:dynamodb:us-east-1:111111111111:table/bb-arn-mainAccess is deniedAccess is deniedarn:aws:dynamodb:us-east-1:<acct>:table/bb-rv-arn1-nopeMeasured 2026-10-24, admin only:
Access is deniedin every caseAccess is denied; the same ARNs in this account get the shape ValidationExceptionAccess is deniedGetItem with
TableNameset to the canonical table ARN, no-policy caller (2026-10-20): denial namingarn:aws:dynamodb:us-east-1:<acct>:table/bb-arn-main.The admin rows already matched on main (the engine tag handler produces them), except the other-account index and backup ARNs, which answered the shape ValidationException. Only those and the no-policy rows changed.
Tests
tests/test_authz_resource_arn.py:test_tag_denial_names_supplied_arn_noperm: the denial for the aws-cn, s3, us-west-2 and nonexistent ARNs carrieson resource: <arn as sent> because no identity-based policy allows. Fails on main (the message carries the rewritten ARN). The nonexistent row passes on main too, because that ARN is already canonical.test_tag_cross_account_is_access_denied_for_every_principal: a no-policy caller and an admin both getAccess is deniedfor another account's table ARN. Fails on main for the no-policy caller.test_tag_cross_account_index_and_backup_arns_are_access_denied: an admin getsAccess is deniedfor another account's index and backup ARNs. Fails on main, and with the account check moved back after the shape check.test_get_item_table_arn_in_tablename_denial_names_table_arn: pins the measured GetItem message. Passes on main as well (the canonical ARN is both spellings).Unit tests:
supplied_resource_tests(5 tests) andauthorization::resource_tag_tests::denial_names_the_supplied_resource(user and role session).Backends: the change is in the server's authorization layer and runs identically on every backend. The authorization wire files were run on this branch's head on SQLite and PostgreSQL.
Interactions with open PRs
fix/stream-arn-account-check: conflicts incrates/server/src/request_helpers.rsin the tag-operation block (fix(server): refuse stream ARNs from another account or region #466 passesaccount_idtovalidate_tag_resource_arn_shapeand the region and account tovalidate_operation_arns; this branch callscheck_resource_accountbefore the shape check). Resolution: keep this branch's call first, then fix(server): refuse stream ARNs from another account or region #466's two calls; the two account checks give the same answer and fix(server): refuse stream ARNs from another account or region #466's covers stream ARNs only.authz_resource.rsandtests/test_authz_resource_arn.pymerge without conflict, but fix(server): refuse stream ARNs from another account or region #466's test helper incrates/server/src/authz_resource/stream_scope.rsreadst[0].1fromauthz_targets, which this branch changes toAuthzTarget; whichever PR merges second changes that line tot[0].resource.clone(). With both changes, the merged tree passes the server unit tests and both PRs' wire tests. fix(server): refuse stream ARNs from another account or region #466 also authorizes a tag operation's stream ARN as spelled; with this branch that ARN is then bothresourceandsupplied, so the message is unchanged.Not changed here
aws iam simulate-custom-policyon 2026-10-24 for a user and an assumed-role caller, an Allow on the canonical table ARN gives implicitDeny for the aws-cn, s3, us-west-2 and other-account spellings, and a Deny on it does not match them. So in ExtendDB a principal allowed only on the canonical table ARN is authorized for those spellings, and the request then reaches the engine, which refuses it (Invalid TableArn, or ResourceNotFoundException for aTableName). Nothing is served for a resource other than the one authorized; that principal gets a different error from the one the service returns. A Deny on the canonical ARN matches every spelling here, where on the service it matches only the canonical one. This is the behaviour on main; fix(server): refuse stream ARNs from another account or region #466 changes it for stream ARNs on the tag operations.TableName) was not measured for a no-policy caller; they go through the sameAuthzTargetpath.UntagResourceandListTagsOfResourcewith another account's ARN for a no-policy caller were not measured; they share the TagResource path.TableNameon the item operations is not resolved to the table (A table ARN passed as TableName returns ResourceNotFoundException #442).