diff --git a/test/s3/iam/STS_DISTRIBUTED.md b/test/s3/iam/STS_DISTRIBUTED.md index 4d3edaf32..01c8ebbd3 100644 --- a/test/s3/iam/STS_DISTRIBUTED.md +++ b/test/s3/iam/STS_DISTRIBUTED.md @@ -298,6 +298,54 @@ User Request → Load Balancer → Any S3 Gateway Instance Allow/Deny Request ``` +## Trust Policy Conditions + +Step 5 above evaluates the role's trust policy against context keys derived from the +OIDC token's claims. The available keys are: + +| Condition key | Source | +|---------------|--------| +| `oidc:iss` | `iss` claim (issuer URL) | +| `oidc:sub` | `sub` claim | +| `oidc:aud` | `aud` claim | +| `oidc:` | any other token claim, e.g. `oidc:roles`, `oidc:groups`, `oidc:email` | +| `aws:FederatedProvider` | the provider `name` (e.g. `keycloak-oidc`) when its configured issuer matches the token, otherwise the raw issuer URL | +| `aws:userid` | `sub` claim (same value as `oidc:sub` during trust-policy evaluation) | +| `sts:DurationSeconds` | requested session duration, when supplied | + +During trust-policy evaluation `aws:userid` is the raw `sub` claim. Once the +role has been assumed, the keys seen by request authorization differ: there +`aws:userid` is a stable per-identity hash of `sub` and `iss` (see +`ComputeParentUser`), so do not assume the two contexts carry the same value. + +Custom claims are always exposed under the `oidc:` prefix, so a trust policy must use +`oidc:roles` (not a bare `roles`) to match a `roles` claim: + +```json +"Condition": { + "StringEquals": { + "oidc:roles": "s3-admin" + } +} +``` + +A multi-valued claim (such as a `roles` array) matches when any of its values equals +the condition value. The same `oidc:` keys can be interpolated into policy resources, +e.g. `arn:aws:s3:::bucket/${oidc:sub}/*`. + +### roleMapping vs. trust policy + +A provider's `roleMapping` and a role's trust policy apply to two different entry +points and are not interchangeable: + +- **Direct OIDC** — an S3 request carrying `Authorization: Bearer `. The + gateway applies `roleMapping` to choose the caller's role from the token claims; the + first matching rule (or `defaultRole`) wins. +- **STS `AssumeRoleWithWebIdentity`** — the caller names the role explicitly via + `RoleArn`, and that role's trust policy decides whether the assumption is allowed. + `roleMapping` does not select the role on this path; instead the token claims are + surfaced as the `oidc:` condition keys above for the trust policy to evaluate. + ## Configuration Management ### Development Environment diff --git a/test/s3/iam/iam_config_docker.json b/test/s3/iam/iam_config_docker.json index a533b16d7..0d6466104 100644 --- a/test/s3/iam/iam_config_docker.json +++ b/test/s3/iam/iam_config_docker.json @@ -37,7 +37,7 @@ "Action": ["sts:AssumeRoleWithWebIdentity"], "Condition": { "StringEquals": { - "roles": "s3-admin" + "oidc:roles": "s3-admin" } } } @@ -60,7 +60,7 @@ "Action": ["sts:AssumeRoleWithWebIdentity"], "Condition": { "StringEquals": { - "roles": "s3-read-only" + "oidc:roles": "s3-read-only" } } } @@ -83,7 +83,7 @@ "Action": ["sts:AssumeRoleWithWebIdentity"], "Condition": { "StringEquals": { - "roles": "s3-read-write" + "oidc:roles": "s3-read-write" } } }