Skip to content

fix(server): name the supplied ARN in a denial - #509

Open
LeeroyHannigan wants to merge 2 commits into
mainfrom
fix/denial-names-supplied-arn
Open

LeeroyHannigan wants to merge 2 commits into
mainfrom
fix/denial-names-supplied-arn

Conversation

@LeeroyHannigan

Copy link
Copy Markdown
Collaborator

Summary

A caller with no policy who names a resource by an ARN in another partition, service or region gets an AccessDeniedException whose 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 answers Access is denied to 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_targets returns AuthzTarget { action, resource, supplied }.
    • resource is the canonical ARN for this deployment and is what the policies are matched against, as before.
    • supplied is what a denial names. It is the request field as written when the field parses as a DynamoDB resource ARN (ResourceArn, StreamArn, the export TableArn, SourceTableArn, a table ARN in TableName, a RequestItems key or a TransactItems table; a BackupArn is trimmed first, as the engine trims it). Otherwise it is the canonical ARN: for a bare name, and for an index Query or Scan whose TableName is not a table ARN.
    • check_resource_account refuses a table/... ARN whose account is not the caller's with AccessDeniedException "Access is denied".
  • crates/server/src/authorization.rs: check_authorization takes an AuthzTarget; the policy evaluation uses target.resource and deny_message names target.supplied.
  • crates/server/src/request_helpers.rs: the tag operations run check_resource_account first, then the existing ResourceArn shape check, both before the IAM decision; every check_authorization call 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, table bb-arn-main. The TagResource rows were measured three times on 2026-10-20 and 2026-10-21, with identical answers.

ResourceArn admin no-policy caller
arn:aws-cn:dynamodb:us-east-1:<acct>:table/bb-arn-main ValidationException Invalid TableArn: Invalid ResourceArn provided as input <arn> AccessDeniedException 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 action
arn:aws:s3:us-east-1:<acct>:table/bb-arn-main same ValidationException same denial, resource arn:aws:s3:us-east-1:<acct>:table/bb-arn-main
arn:aws:dynamodb:us-west-2:<acct>:table/bb-arn-main same ValidationException same denial, resource arn:aws:dynamodb:us-west-2:<acct>:table/bb-arn-main
arn:aws:dynamodb:us-east-1:111111111111:table/bb-arn-main AccessDeniedException Access is denied AccessDeniedException Access is denied
arn:aws:dynamodb:us-east-1:<acct>:table/bb-rv-arn1-nope ResourceNotFoundException denial, resource as supplied

Measured 2026-10-24, admin only:

request answer
TagResource with another account's ARN combined with aws-cn, s3 or us-west-2, a nonexistent name, or a 2-character name; with an empty, non-numeric or 11-digit account AccessDeniedException Access is denied in every case
TagResource with another account's index ARN or backup ARN AccessDeniedException Access is denied; the same ARNs in this account get the shape ValidationException
UntagResource and ListTagsOfResource with another account's ARN AccessDeniedException Access is denied

GetItem with TableName set to the canonical table ARN, no-policy caller (2026-10-20): denial naming arn: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 carries on 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 get Access is denied for 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 gets Access is denied for 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) and authorization::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

Not changed here

  • A table ARN supplied with another partition, service or region is still matched against the canonical ARN for the IAM decision. IAM matches the ARN as written: measured with aws iam simulate-custom-policy on 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 a TableName). 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.
  • Which spelling the denial names for operations other than TagResource (DescribeStream, DescribeBackup, export, a respelled table ARN in TableName) was not measured for a no-policy caller; they go through the same AuthzTarget path.
  • UntagResource and ListTagsOfResource with another account's ARN for a no-policy caller were not measured; they share the TagResource path.
  • A table ARN in TableName on the item operations is not resolved to the table (A table ARN passed as TableName returns ResourceNotFoundException #442).

…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.

This branch has not been deployed

No deployments
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.

AccessDenied for an ARN in another partition, service, region, or account names the ARN rewritten to this deployment

1 participant