Repository navigation
IAM Policies
Note
Identity policies are evaluated on the S3 data plane only when the gateway runs with --iam-standalone-endpoint. The built-in IAM backends (--iam-dir, LDAP, Vault, FreeIPA, S3) have no identity-policy layer at all: under them anything short of a bucket-policy or ACL Allow yields the bare Access Denied. This page describes identity policies as implemented by the standalone IAM service.
The entire policy-management surface is eight inline-policy actions — PutUserPolicy, GetUserPolicy, DeleteUserPolicy, ListUserPolicies and the four …RolePolicy equivalents. There are no policy ARNs, no versioning, no attach/detach, no permissions boundaries and no groups: CreatePolicy, AttachUserPolicy, CreateGroup, SimulatePrincipalPolicy and the rest are absent from the router, so calling one returns InvalidAction, HTTP 400, Could not find operation CreatePolicy for version 2010-05-08. IAM API has the full inventory of what is and is not routed; Incompatibilities with AWS S3 frames the AWS contrast.
Managed session policies are rejected explicitly rather than ignored: any PolicyArns.member.N on AssumeRoleWithWebIdentity returns InvalidInput, HTTP 400, PolicyArns is not supported by this implementation.
A role's trust policy (AssumeRolePolicyDocument) is a different document type with a different grammar — see STS & Web Identity.
Point the AWS CLI at the standalone IAM service's public API (the address given by the global --port flag, :7070 by default), not at the S3 gateway:
aws --endpoint-url http://iam.example.com:7070 iam put-user-policy \
--user-name alice \
--policy-name reports-readonly \
--policy-document file://reports-readonly.json- A policy name is at most 128 characters and must match
^[A-Za-z0-9+=,.@_-]+$; a/is not allowed. Anything else givesValidationError/ HTTP 400:The specified value for policyName is invalid. It must contain only alphanumeric characters and/or the following: +=,.@_- - Policy names are matched exactly and case-sensitively, unlike user and role names which are matched case-insensitively.
Reportsandreportsare two different policies. - A user or role cannot be deleted while it still holds inline policies:
DeleteConflict, HTTP 409,Cannot delete entity, must delete policies first.
| Limit | Value |
|---|---|
| Inline policy bytes per user | 2048, aggregate across all of the user's inline policies |
| Inline policy bytes per role | 10240, aggregate across all of the role's inline policies |
| Session policy and trust policy | 2048 bytes each |
The PolicyDocument request parameter is itself capped at 131072 bytes. The per-entity quota is an aggregate, not a per-policy limit: the check sums the incoming document plus every other inline policy on that entity, so the policy being replaced is correctly excluded and re-writing an existing policy at the same size always succeeds. Overflow returns LimitExceeded, HTTP 409 Conflict, Maximum policy size of 2048 bytes exceeded for user alice. IAM API carries the full quota reference.
Important
The document is stored as the exact bytes you submitted, so pretty-printing counts against the quota. Minifying a JSON document typically frees 30–40% of it.
An identity policy is a JSON object. The parser is strict in ways that catch real mistakes:
- A statement may be a single object or an array of objects.
-
Unknown fields inside a statement are rejected. A typo such as
"Conditon"is a hard error, not a silently unconditional Allow. -
Duplicate JSON keys are rejected at every nesting depth — top level, inside a statement, inside a
Conditionoperator or key map.{"Effect":"Deny","Effect":"Allow"}is refused rather than silently keeping the last one. - Unknown top-level document fields are accepted and ignored, so a document carrying an
Idelement round-trips unchanged.
| Element | Notes |
|---|---|
Version |
Optional. Absent, "2008-10-17" or "2012-10-17"; not defaulted when absent. |
Sid |
Optional. Must be unique within the document when non-empty. |
Effect |
Required. Exactly "Allow" or "Deny", case-sensitive. |
Action / NotAction
|
Exactly one is required; they are mutually exclusive. Vendor-prefixed or "*". |
Resource / NotResource
|
Exactly one is required and neither may be an empty array; they are mutually exclusive. "*" or ARN-shaped. |
Principal / NotPrincipal
|
Forbidden. |
-
Always write
"Version": "2012-10-17". Policy variables are substituted only under exactly that string; under any other version, and with no version at all, a${...}in aResourceis matched as literal text and therefore never matches a real ARN. -
Sidcarries no character-set or length restriction in this implementation. Uniqueness is per document and only for non-empty Sids, so two inline policies on the same user may reuse one. - Action matching is case-insensitive (
"S3:GETOBJECT"matchess3:GetObject); resource matching is case-sensitive. The part after the action's:is never checked against any catalogue, so a misspelled action grants nothing and reports no error — check what you wrote against Supported actions. - A resource ARN's partition must be
aws.aws-cn,aws-us-govand theaws-iso*partitions are deliberately not supported. The service, region, account and resource-id fields are not validated, soarn:aws:notaservice:::anythingpasses. - A wildcard crosses
:and/freely, soarn:aws:s3:::team-*covers every bucket whose name starts withteam-and every object in them. For a prefix-scoped grant that cannot leak into a neighbouring bucket, write the bucket name literally and put the wildcard only after the/. - An optional
Conditionblock is supported; Policy Conditions has the operators, the keys and what happens when a key is absent.
Principal and NotPrincipal are rejected with MalformedPolicyDocument: Policy document should not specify a principal., and that check runs before the Action/Resource checks — so a bucket policy pasted into put-user-policy reports the principal error even when it also has other problems.
Going the other way, a bucket policy does require a Principal, and under standalone IAM that is an AWS-style principal ARN — a user, role or assumed-role session ARN, the account root ARN or the bare account id, or * — never an access key ID and never an IAM user name. See Bucket Policies.
All of these are code MalformedPolicyDocument, type Sender, HTTP 400.
| Message | Cause |
|---|---|
Syntax errors in policy. |
Unparseable JSON, a duplicate JSON key, an unknown field inside a statement, an unsupported Version, an empty or missing Statement, a bad Effect, both Action and NotAction, both Resource and NotResource, or an unrecognized Condition operator name |
Policy statement must contain actions. |
Neither Action nor NotAction
|
Policy statement must contain resources. |
Neither Resource nor NotResource, or "Resource": []
|
Policy document should not specify a principal. |
Principal or NotPrincipal present |
Statement IDs (SID) in a single policy must be unique. |
Two statements share a non-empty Sid
|
Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc. |
An action with no :
|
Vendor s 3 is not valid |
The text before the first : does not match ^[A-Za-z0-9-]+$
|
Resource mybucket must be in ARN format or "*". |
A resource with no :
|
The policy failed legacy parsing |
An arn:-prefixed resource with fewer than five remaining :-separated fields |
Partition "aws-cn" is not valid for resource "arn:aws-cn:s3:::b/*". |
A partition other than aws
|
Two other rejections happen before the grammar is even parsed, both code ValidationError / HTTP 400:
- Over 131072 bytes:
1 validation error detected: Value at 'policyDocument' failed to satisfy constraint: Member must have length less than or equal to 131072 - An empty document, a control character, or a character above
U+00FF(an emoji, for instance):The specified value for policyDocument is invalid. It must contain only printable ASCII characters.Tab, LF and CR are allowed.
The user or role must already exist: the entity lookup runs before the document is parsed, so a PutUserPolicy for an unknown user reports NoSuchEntity even when the document is also malformed.
Every ARN the service generates and every ARN you write uses the fixed account ID 000000000000 — twelve zeros. An assumed-role session uses the sts service and the assumed-role/ prefix, arn:aws:sts::000000000000:assumed-role/<RoleName>/<RoleSessionName>; that is the ARN a session's denial messages name, and the one a bucket policy uses to name one specific session.
aws:PrincipalArn for a session is the assumed role's ARN, arn:aws:iam::000000000000:role/<RoleName> — not the session ARN. The role ARN is also the bucket-policy principal that covers every session of that role. Standalone IAM has the full entity ARN table.
The grammar accepts any syntactically valid vendor:Action, but only the names below are ever evaluated — anything else in a policy is inert. s3:* is the catch-all over every S3 action.
Object-level, evaluated against arn:aws:s3:::<bucket>/<key>: s3:GetObject, s3:GetObjectVersion, s3:PutObject, s3:DeleteObject, s3:DeleteObjectVersion, s3:GetObjectAcl, s3:PutObjectAcl, s3:GetObjectAttributes, s3:GetObjectVersionAttributes, s3:RestoreObject, s3:GetObjectTagging, s3:PutObjectTagging, s3:DeleteObjectTagging, s3:GetObjectVersionTagging, s3:PutObjectVersionTagging, s3:DeleteObjectVersionTagging, s3:GetObjectLegalHold, s3:PutObjectLegalHold, s3:GetObjectRetention, s3:PutObjectRetention, s3:BypassGovernanceRetention, s3:AbortMultipartUpload, s3:ListMultipartUploadParts
Bucket-level, evaluated against arn:aws:s3:::<bucket>: s3:CreateBucket, s3:DeleteBucket, s3:ListBucket, s3:ListBucketVersions, s3:ListBucketMultipartUploads, s3:GetBucketLocation, s3:GetBucketPolicyStatus, and the paired Get/Put actions s3:GetBucketAcl, s3:GetBucketVersioning, s3:GetBucketTagging, s3:GetBucketObjectLockConfiguration, s3:GetBucketOwnershipControls, s3:GetBucketCORS, s3:GetAnalyticsConfiguration, s3:GetEncryptionConfiguration, s3:GetIntelligentTieringConfiguration, s3:GetInventoryConfiguration, s3:GetLifecycleConfiguration, s3:GetBucketLogging, s3:GetBucketRequestPayment, s3:GetMetricsConfiguration, s3:GetReplicationConfiguration, s3:GetBucketPublicAccessBlock, s3:GetBucketNotification, s3:GetAccelerateConfiguration, s3:GetBucketWebsite — each with its Put… counterpart, plus s3:DeleteBucketPolicy, s3:DeleteBucketWebsite and s3:GetBucketPolicy / s3:PutBucketPolicy
Account-level, evaluated against arn:aws:s3:::*: s3:ListAllMyBuckets
The mappings that are not obvious from the name:
-
s3:ListBucketalso coversHeadBucket;s3:ListBucketVersionscovers version listing. -
s3:CreateBucketis evaluated againstarn:aws:s3:::<name>— the bucket does not exist yet, so no bucket policy is in play. -
s3:GetBucketCORS/s3:PutBucketCORSuse the uppercaseCORSspelling. - Starting and continuing a multipart upload is authorized as
s3:PutObjecton the object ARN. -
s3:DeleteObjectands3:DeleteObjectVersionare separate permissions. Any request carrying aversionIdis authorized againsts3:DeleteObjectVersion, and because the check runs at authorization time before the backend resolves the version, even a nonsenseversionIdroutes to the versioned permission. -
s3:prefixis populated only when the request actually carries aprefixquery parameter, so aStringLikecondition on it does not match a bareaws s3 ls s3://bucketand that listing is denied. See Policy Conditions.
s3:ListAllMyBuckets behaves unlike every other action here:
- It is identity-policy only, deliberately absent from the bucket-policy action list, so a
PutBucketPolicynaming it is rejected withMalformedPolicy/Policy has invalid action. - It is always evaluated against
arn:aws:s3:::*, never a particular bucket. AnAllowonarn:aws:s3:::mybucketgrants nothing; usearn:aws:s3:::*or"*". - The listing is not filtered per user: a caller holding it sees every bucket on the gateway, each reporting the root account's access key as its owner. See Standalone IAM.
An identity policy also governs the IAM control plane, where the action string is iam: plus the API action name. The set of names is exactly the actions indexed in IAM API, which groups them identically. The resource each is evaluated against depends on the action:
- Actions naming a specific user or role are evaluated against that entity's stored ARN, with its tags available as
iam:ResourceTag/<key>andaws:ResourceTag/<key>. -
iam:GetUser,iam:CreateAccessKey,iam:UpdateAccessKey,iam:DeleteAccessKeyandiam:ListAccessKeysaccept an omittedUserNameand are then evaluated against the caller's own stored user ARN and tags, so a self-service policy scoped toarn:aws:iam::000000000000:user/<self>authorizes them without a wildcardResource. A caller with no IAM user of its own — an assumed-role session, or root — resolves to no resource, which only a wildcardResourcestatement matches; the controller then rejects the request anyway withValidationError/Must specify userName when calling with non-User credentials. -
iam:CreateUserandiam:CreateRoleare evaluated against the ARN the request would create, built from theUserName/RoleNameandPathparameters. - Every OIDC provider action carrying an
OpenIDConnectProviderArn—iam:GetOpenIDConnectProvider,iam:DeleteOpenIDConnectProvider,iam:AddClientIDToOpenIDConnectProvider,iam:RemoveClientIDFromOpenIDConnectProvider,iam:UpdateOpenIDConnectProviderThumbprint,iam:TagOpenIDConnectProvider,iam:UntagOpenIDConnectProviderandiam:ListOpenIDConnectProviderTags— is evaluated against that raw ARN parameter, with the stored provider's own tags exposed asiam:ResourceTag/<key>andaws:ResourceTag/<key>.iam:CreateOpenIDConnectProvideris evaluated against the ARN built from the validatedUrl. AResourceofarn:aws:iam::000000000000:oidc-provider/*therefore does scope provider management. -
iam:ListUsers,iam:ListRoles,iam:ListOpenIDConnectProvidersand anything the middleware does not specifically recognize are evaluated against"*". -
iam:UpdateUseris authorized twice — once against the user's current ARN and once against the ARN a rename or path change would produce. Both must Allow.
aws:RequestTag/<key> and aws:TagKeys are populated from the request's Tags parameter for iam:CreateUser, iam:CreateRole, iam:TagUser, iam:TagRole, iam:CreateOpenIDConnectProvider and iam:TagOpenIDConnectProvider; iam:UntagUser, iam:UntagRole and iam:UntagOpenIDConnectProvider supply keys with no values and so populate aws:TagKeys alone. That is what lets a tag-scoped Condition govern which tags a caller may set or remove, not only which tags a caller may create an entity with. A malformed Tags/TagKeys parameter leaves these keys unpopulated here; the controller rejects the request with the specific tag error afterwards.
The two STS actions, sts:AssumeRoleWithWebIdentity and sts:GetCallerIdentity, are exempt from identity-policy enforcement and reach the controller directly, matching real AWS. sts: is the only vendor prefix allowed in a trust policy (see STS & Web Identity), but writing sts: actions into an identity policy has no effect.
The root account bypasses identity-policy evaluation entirely on both the IAM control plane and the S3 data plane. Root never needs a policy and cannot be restricted by one.
A ${key} reference is substituted with the single value of that condition key from the request context. Substitution happens in Resource/NotResource patterns and in string and ARN condition operands only — never in Action/NotAction, and never in a trust policy's Principal — and only under "Version": "2012-10-17" exactly. A ${...} naming a key that is absent, or that has more than one value, is left as literal text rather than dropped, so the statement stops matching instead of silently widening.
${aws:username} is not set for an assumed-role session, so the classic home-prefix pattern arn:aws:s3:::home/${aws:username}/* silently stops working once a caller assumes a role. Use ${aws:userid} or a ${aws:PrincipalTag/<key>} value there instead. The full variable and condition-key inventory is in Policy Conditions.
This is what actually decides an S3 request.
-
Readonly gate. Under
--readonly, a request needing write or write-ACL permission is denied with the bareAccessDenied/Access Denied.for everyone, root and admin included, before any bypass and with no policy able to grant past it — includingCopyObject, which previously returned early for root and admin and so escaped the check. - Root, admin and public bypass. Policy and ACL evaluation is skipped for the root account, for any account carrying the admin role, and for an anonymous request already authorized by the public-bucket check; object locks still apply to all of them. Under standalone IAM every resolved account is a plain user, so in practice only root bypasses.
-
Resource decision. The bucket policy is fetched once. If one exists it decides and the bucket ACL is not consulted at all; with no bucket policy the ACL decides and can produce only
AlloworNoMatch, since ACLs have no explicit deny and their verdict applies uniformly to the whole bucket. For a multi-action request aDenyon any one action wins immediately, andAllowrequires every one of them. -
A bucket-policy explicit
Denyshort-circuits a single-resource request: it fails there, and noevaluate-policycall is made to the standalone IAM service at all. In aDeleteObjectsbatch the deny settles only the keys it matches and the round trip is still made — see DeleteObjects partial success. -
Identity decision. Performed only under
--iam-standalone-endpoint: oneevaluate-policyrequest carrying the whole actions × resources matrix over the private endpoint. With a built-in IAM backend this step does not exist, and anything short of a bucket-policy or ACLAllowis the bareAccess Denied. -
An explicit
Denyfrom the identity policy or from a session policy denies the request. An explicit deny in any layer beats everyAllowin every other layer. -
Allowis granted if either the resource decision or the identity decision isAllow. They are independent sources; neither has to agree with the other. - A session policy filters everything, including a grant that came from the bucket policy rather than from the role.
- Otherwise: implicit deny.
- Object locks are checked separately, after and independently of all of the above, and they apply to root as well — see Object lock interaction.
| Identity policy | Bucket policy | Outcome |
|---|---|---|
| silent | silent | Implicit deny, naming the identity ARN |
| Allow | silent | Allowed |
| silent | Allow | Allowed |
| Allow | Allow | Allowed |
| Deny | silent | Explicit deny in an identity-based policy |
| Deny | Allow | Explicit deny in an identity-based policy |
| silent | Deny | Explicit deny in a resource-based policy |
| Allow | Deny | Explicit deny in a resource-based policy |
| Deny | Deny | Explicit deny in a resource-based policy — the bucket policy is evaluated first and returns immediately |
"Silent" means the policy exists but no statement matched, or there is no policy at all.
- Under standalone IAM a freshly created bucket is owned by root and grants an ordinary user nothing through its ACL, so the resource decision is
NoMatchby default. The identity policy must Allow, or the request is denied. - A bucket policy can grant access to a user with no identity policy at all — but only from a statement naming the caller directly (its ARN, its role's ARN, or
*). A statement whosePrincipalnames the account,arn:aws:iam::000000000000:rootor the bare000000000000, is a delegation: underAllowit contributes nothing and the identity policy must still allow the request, while underDenyit denies every principal in the account outright. See Bucket Policies.
Every denial is code AccessDenied with HTTP 403, and the message tells you which layer decided. The first three share the prefix User: <principal> is not authorized to perform: <action> on resource: "<arn>":
| Suffix, or whole message | What happened |
|---|---|
with an explicit deny in a resource-based policy |
A Deny statement in the bucket policy matched |
with an explicit deny in an identity-based policy |
A Deny statement in an inline identity policy or a session policy matched |
because no identity-based policy allows the <action> action |
Nothing granted it — an implicit deny |
Access Denied. |
Either --readonly blocked a write, or the gateway is running a built-in IAM backend with no identity-policy layer |
Access Denied because object protected by object lock. |
An object lock, not a policy |
<principal> is the caller's ARN — arn:aws:iam::000000000000:user/alice, or arn:aws:sts::000000000000:assumed-role/data-reader/session-1 for a session — in every case, resource-based denials included. The raw access key id appears instead only when the IAM backend gives the caller no ARN, which is every backend except the standalone service.
The <action> named in an implicit deny is the first action of the request that lacked an Allow. A request such as PutObject with an x-amz-tagging header is authorized against s3:PutObject and s3:PutObjectTagging at once, and all of them must pass — so a denial naming s3:PutObjectTagging on what looked like a plain upload is not a bug.
The evaluator refuses to guess. Each of these produces a Deny for the whole evaluation, not merely a skipped statement:
- A stored policy document that cannot be parsed as JSON.
- A stored policy document that fails validation. Validation is re-run at the security boundary, not just at write time, so an old document written before a validation rule existed is caught. A single invalid statement invalidates the entire document for every request, including requests that statement would never have covered.
- A
Conditionblock that cannot be evaluated — an unparseable block or an unrecognized operator name. This is notDeny-statement-specific: an unknown operator in an unrelatedAllowdenies everything the document might otherwise have granted. - A mismatch between the decision matrix the IAM service returns and the request the gateway sent.
- The standalone IAM service being unreachable, which surfaces as
InternalError/ HTTP 500 rather than as an allow.
So if one user suddenly cannot do anything at all, suspect one broken statement in one of their inline policies rather than a missing grant.
Temporary credentials minted by AssumeRoleWithWebIdentity (access key IDs prefixed ASIA…) draw their permissions from the role's inline policies, not from any user. If the call supplied a Policy parameter, that session policy is evaluated as its own document with the same grammar and semantics, and the two are intersected:
effective = (role identity policies ∪ bucket policy) ∩ session policy
An explicit Deny on either side denies outright, and a session policy can only narrow, never widen. It filters everything, including a grant that came from the bucket policy rather than from the role: a role with no identity policy, a bucket policy granting both s3:GetObject and s3:PutObject, and a session policy allowing only s3:GetObject yields a working GetObject and a denied PutObject.
On the S3 data plane an expired session is reported as InvalidAccessKeyId, not as an authorization failure. STS & Web Identity covers the rest: the intersection table, the 2048-byte session-policy cap, why an ASIA… key can never be an ACL grantee or a bucket owner while a session is still nameable by ARN in a bucket policy, why the role's tags are the session's tags for its whole lifetime, and what happens to live sessions once their role is deleted.
DeleteObjects is the one write path that supports partial success, and the gateway authorizes it accordingly. Every key in the batch is evaluated independently, against its own object ARN, so a denial removes that key from the batch rather than failing the whole request:
- The XML body is parsed before authorization, because the keys are part of what is being authorized.
- The batch is split into keys with no
versionId(authorized ass3:DeleteObject) and keys with one (s3:DeleteObjectVersion) — oneevaluate-policyround trip per group, never one per object. - Object lock state is loaded once and applied per key, skipping keys already denied by policy so a policy denial is never masked by a less specific lock error. Only error-free keys reach the backend, and the results are merged preserving request order across both lists.
The client sees a normal 200 with a DeleteResult body carrying both outcomes; a denied key appears as:
<Error>
<Key>denied/three</Key>
<Code>AccessDenied</Code>
<Message>User: arn:aws:iam::000000000000:user/alice is not authorized to perform: s3:DeleteObject on resource: "arn:aws:s3:::mybucket/denied/three" because no identity-based policy allows the s3:DeleteObject action</Message>
</Error>A governance-locked key in the same batch is reported the same way, with Access Denied because object protected by object lock.
Only two things fail the whole request rather than one key: --readonly mode, and an error resolving the bucket policy or lock configuration.
A bucket-policy explicit Deny is per-key like every other denial. It settles the key it matches — reported with the resource-based message, and never overridden by an identity policy that would have allowed it — and nothing else: every other key in the batch is still evaluated on its own, identity policy included. A batch in which the bucket policy denies every key is the one case that skips the evaluate-policy round trip, since no identity-policy answer could change any of the results.
Note
In v1.7.0 and earlier this short-circuited: the first denied key ended the batch's evaluation, and every later key in that action group was sent to the backend without its identity policy ever being evaluated. On those versions, do not rely on a bucket-policy Deny as the only control on a bucket that also accepts multi-object deletes — put the Deny in the identity policy as well.
Object locks are enforced after policy, and unlike policy they are not bypassed by root. Whether a GOVERNANCE retention can be overridden depends on how the request arrived, which the gateway models as three bypass modes:
| Mode | When |
|---|---|
BypassNone |
The request sent no x-amz-bypass-governance-retention header, so any unexpired retention blocks it |
BypassRequested |
DeleteObject, DeleteObjects or PutObjectRetention with x-amz-bypass-governance-retention: true
|
BypassOverwrite |
PutObject, CopyObject or POST Object silently replacing a locked object — none of these has a bypass header for a client to send, so the gateway supplies the mode itself |
BypassOverwrite is a versitygw extension with no AWS analogue: real S3 only allows object lock on versioned buckets, where an overwrite creates a new version instead of replacing a locked object. On an unversioned bucket the gateway would otherwise destroy a locked object without anybody having asked to.
Who may proceed:
| Situation | Root and admin | Ordinary caller |
|---|---|---|
GOVERNANCE object, bypass header sent (BypassRequested) |
Always allowed | Needs an Allow for s3:BypassGovernanceRetention on the object |
GOVERNANCE object, no bypass header (BypassNone) |
Blocked | Blocked — Access Denied because object protected by object lock.
|
GOVERNANCE object replaced by a PutObject/CopyObject/POST (BypassOverwrite) |
Needs a bucket-policy grant for s3:BypassGovernanceRetention |
Needs an Allow for s3:BypassGovernanceRetention
|
| COMPLIANCE object, any mode | Blocked | Blocked |
| Legal hold ON, any mode | Blocked | Blocked |
- Root's blanket override applies only to
BypassRequested, so an accidental overwrite of a locked object is refused even for root. Because root is not an IAM user, itsBypassOverwritecheck falls back to the bucket policy alone and needs an explicit bucket-policyAllowfors3:BypassGovernanceRetention. - The denial naming
s3:BypassGovernanceRetentionappears only when the bypass header was sent and permission was missing. A request with no bypass header, an anonymous request, or a gateway with no identity-policy layer gets the genericAccess Denied because object protected by object lock.instead.
PutObjectRetention distinguishes strengthening a lock from weakening one:
| Stored retention | Requested retention | Required |
|---|---|---|
| none | anything | s3:PutObjectRetention |
| GOVERNANCE until T | GOVERNANCE until ≥ T |
s3:PutObjectRetention — an extension, no header needed |
| COMPLIANCE until T | COMPLIANCE until ≥ T |
s3:PutObjectRetention — an extension, no header needed |
| GOVERNANCE until T | GOVERNANCE until < T | Bypass header and s3:BypassGovernanceRetention
|
| GOVERNANCE until T | COMPLIANCE, any date | Bypass header and s3:BypassGovernanceRetention — a mode change is never an extension |
| COMPLIANCE until T | anything shorter, or GOVERNANCE at any date | Refused for everyone, header or not |
The same date counts as an extension, not a weakening. A stored retention carrying no date at all is treated as weakenable, failing closed. A RetainUntilDate in the past is rejected outright as an invalid argument.
Delegates user administration for one team, using iam:ResourceTag to scope management to users tagged team=alpha and aws:RequestTag to force new users to carry that tag.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InspectAnyUser",
"Effect": "Allow",
"Action": ["iam:ListUsers", "iam:GetUser"],
"Resource": "*"
},
{
"Sid": "CreateOnlyTeamAlphaUsers",
"Effect": "Allow",
"Action": "iam:CreateUser",
"Resource": "arn:aws:iam::000000000000:user/*",
"Condition": {
"StringEquals": { "aws:RequestTag/team": "alpha" }
}
},
{
"Sid": "ManageTeamAlphaUsers",
"Effect": "Allow",
"Action": [
"iam:CreateAccessKey",
"iam:DeleteAccessKey",
"iam:ListAccessKeys",
"iam:PutUserPolicy",
"iam:GetUserPolicy",
"iam:DeleteUserPolicy",
"iam:ListUserPolicies"
],
"Resource": "arn:aws:iam::000000000000:user/*",
"Condition": {
"StringEquals": { "iam:ResourceTag/team": "alpha" }
}
}
]
}- By the resource rules above, a
create-usercall withoutTags=[{Key=team,Value=alpha}]is denied, and the management statement reaches only users already taggedteam=alpha. An untagged user has noiam:ResourceTag/teamkey at all, soStringEqualsfails and the statement does not match. -
iam:ListUsersis always evaluated against"*", which is why it needs its own statement — aResourceofarn:aws:iam::000000000000:user/*would not match it. - The delegation is deliberately narrow: it grants no
iam:TagUser,iam:UntagUseroriam:DeleteUser, so the delegated admin cannot retag a user into or out of their own scope. Addingiam:TagUserwould let them widen it.
Home · Quickstart · Configuration · Operations · Testing · Developer Guide · GitHub · Discussions · Issues
Apache 2.0 · @versitysoftware · LinkedIn · X · Facebook · Instagram
- Home
- Key Features
- User Guide
- Getting Started
- Networking and Deployment
- Access Control / IAM
- Features
- Backends
- Compatibility
- Operations
- Metrics
- Admin APIs
- Logging
- S3 RDMA
- Developer Guide
- Articles