This is an automated email from the ASF dual-hosted git repository.

jojochuang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone-site.git


The following commit(s) were added to refs/heads/master by this push:
     new 61a9617b42 HDDS-15967. [STS] End user documentation (#510)
61a9617b42 is described below

commit 61a9617b42a97b7a4b523a30b400f3ac8373c9c9
Author: fmorg-git <[email protected]>
AuthorDate: Wed Aug 5 23:37:46 2026 -0700

    HDDS-15967. [STS] End user documentation (#510)
---
 .markdownlint.yaml                                 |   1 +
 .../01-architecture/04-s3-gateway.md               |   2 +-
 .../03-namespace/01-volumes/01-overview.md         |   2 +-
 .../03-namespace/02-buckets/07-links.md            |   2 +-
 .../01-multi-tenancy}/01-overview.md               |   2 +-
 .../01-multi-tenancy}/02-setup.md                  |   6 +-
 .../01-multi-tenancy}/03-tenant-commands.md        |   2 +-
 .../01-multi-tenancy}/04-access-control.md         |   2 +-
 .../01-multi-tenancy}/README.mdx                   |   2 +-
 .../03-operations/07-s3/02-sts.md                  | 605 +++++++++++++++++++++
 .../03-operations/07-s3/README.mdx                 |  11 +
 11 files changed, 627 insertions(+), 10 deletions(-)

diff --git a/.markdownlint.yaml b/.markdownlint.yaml
index 35dccc7ef2..27c30652b7 100644
--- a/.markdownlint.yaml
+++ b/.markdownlint.yaml
@@ -45,6 +45,7 @@ no-inline-html:
   - div
   - Tabs
   - TabItem
+  - Details
   - DocCardList
 
 # Allow bare URLs since wrapping URLs in <> is invalid mdx.
diff --git a/docs/03-core-concepts/01-architecture/04-s3-gateway.md 
b/docs/03-core-concepts/01-architecture/04-s3-gateway.md
index a5c2ad9e79..ae7bcb931b 100644
--- a/docs/03-core-concepts/01-architecture/04-s3-gateway.md
+++ b/docs/03-core-concepts/01-architecture/04-s3-gateway.md
@@ -106,6 +106,6 @@ The Ozone S3 Gateway is a **protocol adapter**:
 For more details, refer to the other documentation:
 
 - [Ozone S3 Gateway Interface](../../user-guide/client-interfaces/s3/s3-api)
-- [S3 
Multi-Tenancy](../../administrator-guide/operations/s3-multi-tenancy/overview)
+- [S3 
Multi-Tenancy](../../administrator-guide/operations/s3/multi-tenancy/overview)
 - [Securing S3](../../user-guide/client-interfaces/s3/securing-s3)
 - [Network 
Ports](../../administrator-guide/configuration/basic/network/default-ports)
diff --git a/docs/03-core-concepts/03-namespace/01-volumes/01-overview.md 
b/docs/03-core-concepts/03-namespace/01-volumes/01-overview.md
index e34f2b8b46..118f4bf784 100644
--- a/docs/03-core-concepts/03-namespace/01-volumes/01-overview.md
+++ b/docs/03-core-concepts/03-namespace/01-volumes/01-overview.md
@@ -93,7 +93,7 @@ ACLs can be set and managed using the Ozone CLI. Refer to the 
[Security ACLs doc
 ### S3 Gateway Integration (`/s3v` Volume)
 
 For compatibility with the S3 API, Ozone uses a special volume, typically 
`/s3v`. By default, all buckets accessed via the S3 interface are stored under 
this volume. It's also possible to expose buckets from other Ozone volumes via 
the S3 interface using "bucket linking."
-For more details, refer to the [S3 Protocol 
documentation](../../../user-guide/client-interfaces/s3/s3-api) and [S3 
Multi-Tenancy 
documentation](../../../administrator-guide/operations/s3-multi-tenancy/overview).
+For more details, refer to the [S3 Protocol 
documentation](../../../user-guide/client-interfaces/s3/s3-api) and [S3 
Multi-Tenancy 
documentation](../../../administrator-guide/operations/s3/multi-tenancy/overview).
 
 ### Datanode Physical Volumes vs. Ozone Manager Logical Volumes
 
diff --git a/docs/03-core-concepts/03-namespace/02-buckets/07-links.md 
b/docs/03-core-concepts/03-namespace/02-buckets/07-links.md
index 715fc02a26..4e81f692fd 100644
--- a/docs/03-core-concepts/03-namespace/02-buckets/07-links.md
+++ b/docs/03-core-concepts/03-namespace/02-buckets/07-links.md
@@ -4,7 +4,7 @@ sidebar_label: Links
 
 # Bucket Links
 
-Bucket linking allows exposing a bucket from one volume (or even another 
bucket) as if it were in a different location, particularly useful for S3 
compatibility or cross-tenant access. This creates a symbolic link-like 
behavior. For more information, see the [S3 Protocol 
documentation](../../../user-guide/client-interfaces/s3) and [S3 Multi-Tenancy 
documentation](../../../administrator-guide/operations/s3-multi-tenancy).
+Bucket linking allows exposing a bucket from one volume (or even another 
bucket) as if it were in a different location, particularly useful for S3 
compatibility or cross-tenant access. This creates a symbolic link-like 
behavior. For more information, see the [S3 Protocol 
documentation](../../../user-guide/client-interfaces/s3) and [S3 Multi-Tenancy 
documentation](../../../administrator-guide/operations/s3/multi-tenancy).
 
 ## Overview
 
diff --git 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/01-overview.md 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/01-overview.md
similarity index 96%
rename from 
docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/01-overview.md
rename to 
docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/01-overview.md
index db98b0b2b5..fb53746505 100644
--- 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/01-overview.md
+++ 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/01-overview.md
@@ -4,7 +4,7 @@ sidebar_label: Overview
 
 # S3 Multi-Tenancy Overview
 
-Before Ozone multi-tenancy, all S3 access to Ozone (via [S3 
Gateway](../../../user-guide/client-interfaces/s3)) were confined to a 
**single** designated S3 volume (that is volume `s3v`, by default).
+Before Ozone multi-tenancy, all S3 access to Ozone (via [S3 
Gateway](../../../../user-guide/client-interfaces/s3)) were confined to a 
**single** designated S3 volume (that is volume `s3v`, by default).
 
 Ozone multi-tenancy allows **multiple** S3-accessible volumes to be created. 
Each volume can be managed separately by their own tenant admins via CLI for 
user operations, and via Apache Ranger for access control.
 
diff --git 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/02-setup.md 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/02-setup.md
similarity index 86%
rename from 
docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/02-setup.md
rename to 
docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/02-setup.md
index 7b542c7446..64bb582462 100644
--- a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/02-setup.md
+++ 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/02-setup.md
@@ -10,9 +10,9 @@ Steps to enable S3 Multi-Tenancy feature in Ozone clusters.
 
 S3 Multi-Tenancy requires the following components to be configured:
 
-1. **Secure cluster**: The cluster must be Kerberized (secured). Follow the 
[Kerberos](../../../administrator-guide/configuration/security/kerberos) guide 
if the cluster is not Kerberized yet.
-2. **S3 Gateway**: At least one S3 Gateway must be set up. Follow the [S3 
Gateway](../../../user-guide/client-interfaces/s3) guide if the cluster doesn't 
have a S3 Gateway yet.
-3. **Apache Ranger**: ACL must be enabled with `RangerOzoneAuthorizer` as the 
effective ACL authorizer implementation. If not configured, follow the 
[Security with 
Ranger](../../../administrator-guide/configuration/security/ranger) guide.
+1. **Secure cluster**: The cluster must be Kerberized (secured). Follow the 
[Kerberos](../../../../administrator-guide/configuration/security/kerberos) 
guide if the cluster is not Kerberized yet.
+2. **S3 Gateway**: At least one S3 Gateway must be set up. Follow the [S3 
Gateway](../../../../user-guide/client-interfaces/s3) guide if the cluster 
doesn't have a S3 Gateway yet.
+3. **Apache Ranger**: ACL must be enabled with `RangerOzoneAuthorizer` as the 
effective ACL authorizer implementation. If not configured, follow the 
[Security with 
Ranger](../../../../administrator-guide/configuration/security/ranger) guide.
 
 ## Configuration
 
diff --git 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/03-tenant-commands.md
 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/03-tenant-commands.md
similarity index 99%
rename from 
docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/03-tenant-commands.md
rename to 
docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/03-tenant-commands.md
index 69452c990a..9996deb606 100644
--- 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/03-tenant-commands.md
+++ 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/03-tenant-commands.md
@@ -361,7 +361,7 @@ As long as the user running this command has the permission 
to create a bucket i
 - The link bucket command will not even check if the source volume and bucket 
exists.
 - Permission check will be performed when the bucket symlink is actually 
accessed.
   - In order to grant a user in tenant `tenantone` access the bucket, a new 
policy should be added by a Ranger admin that allow that user intended 
permissions (`READ, WRITE, LIST, CREATE, DELETE, ...`) to the source bucket 
`bucket1` in volume `vol1`.
-- At the moment, `ozone tenant linkbucket` command is equivalent to `ozone sh 
bucket link` command. See the [Bucket 
Links](../../../core-concepts/namespace/buckets/links) documentation for more 
details.
+- At the moment, `ozone tenant linkbucket` command is equivalent to `ozone sh 
bucket link` command. See the [Bucket 
Links](../../../../core-concepts/namespace/buckets/links) documentation for 
more details.
 
 ## Example: Accessing a bucket via S3 Gateway
 
diff --git 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/04-access-control.md
 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/04-access-control.md
similarity index 97%
rename from 
docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/04-access-control.md
rename to 
docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/04-access-control.md
index 386a58f544..6f1585a81a 100644
--- 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/04-access-control.md
+++ 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/04-access-control.md
@@ -4,7 +4,7 @@ sidebar_label: Access Control
 
 # Access Control with Ranger
 
-Ozone multi-tenancy relies on [Apache 
Ranger](../../../administrator-guide/configuration/security/ranger) to enforce 
access control to resources.
+Ozone multi-tenancy relies on [Apache 
Ranger](../../../../administrator-guide/configuration/security/ranger) to 
enforce access control to resources.
 
 ## Ranger Policies
 
diff --git 
a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/README.mdx 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/README.mdx
similarity index 83%
rename from 
docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/README.mdx
rename to 
docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/README.mdx
index d80ef6ef37..840fc4ad46 100644
--- a/docs/05-administrator-guide/03-operations/07-s3-multi-tenancy/README.mdx
+++ 
b/docs/05-administrator-guide/03-operations/07-s3/01-multi-tenancy/README.mdx
@@ -1,5 +1,5 @@
 ---
-sidebar_label: S3 Multi-Tenancy
+sidebar_label: Multi-Tenancy
 ---
 
 # S3 Multi-Tenancy
diff --git a/docs/05-administrator-guide/03-operations/07-s3/02-sts.md 
b/docs/05-administrator-guide/03-operations/07-s3/02-sts.md
new file mode 100644
index 0000000000..23d147d5e0
--- /dev/null
+++ b/docs/05-administrator-guide/03-operations/07-s3/02-sts.md
@@ -0,0 +1,605 @@
+---
+sidebar_label: STS
+---
+
+# Ozone S3 Security Token Service (STS)
+
+import Details from '@theme/Details';
+
+This guide explains how to enable, configure, and use Ozone STS to issue 
short-lived S3 credentials through the AWS-compatible (at least for the 
supported features) **AssumeRole** API. Ozone STS is designed for scenarios 
such as data-lake workloads that need temporary, scoped access to Ozone buckets 
and keys without distributing long-lived credentials.
+
+## Background
+
+Ozone S3 credentials are normally tied to a Kerberos identity. STS adds a 
programmatic path to obtain **temporary** credentials (access key, secret key, 
and session token) that inherit permissions from a Ranger **role**, optionally 
narrowed further by an inline **IAM session policy**.
+
+The initial implementation:
+
+- Exposes a dedicated STS endpoint on the S3 Gateway (separate from the S3 
object API port).
+- Support the **AssumeRole** API.
+- Requires **Apache Ranger** for authorization.
+
+## Prerequisites
+
+Before using STS, you need:
+
+1. **Secure Ozone cluster** with Kerberos and Ranger enabled.
+2. **Ranger Ozone plugin** installed on OM/S3G with `RangerOzoneAuthorizer` 
configured (see [Configuring Apache 
Ranger](../../../administrator-guide/configuration/security/ranger)).
+3. **Permanent S3 credentials** for the calling user (i.e. service principal):
+
+   ```shell
+   kinit my-service-user
+   ozone s3 getsecret
+   # awsAccessKey=...
+   # awsSecret=...
+   ```
+
+4. **Ozone volume(s), bucket(s) and key(s) created** as needed for the 
resources the STS tokens would need to access
+
+5. **Ranger user, roles and policies** (Ranger Admin configures):
+    - Create a Ranger **user** corresponding to the service principal in Ozone
+    - Create a Ranger **role** for each role that can be assumed.
+    - Grant the calling user **assume_role** permission on that role.
+    - Grant the role the resource permissions and actions it needs 
(volume/bucket/key) via resource policies.
+
+The role name in `RoleArn` must match the Ranger role name:
+
+```text
+arn:aws:iam::123456789012:role/my-data-reader-role
+                              ^^^^^^^^^^^^^^^^^^^^
+                              Ranger role name
+```
+
+The account ID (`123456789012`) is accepted for AWS compatibility but is not 
used for authorization.
+
+When configuring Ranger policies via `curl` (such as in the `Ranger Policy 
Setup` section below), set environment variable `RANGER_URL` to your Ranger 
Admin base URL (for example `http://localhost:6080`) and environment variable 
`RANGER_SERVICE` to your Ozone Ranger service name (for example `dev_ozone`). 
The curl examples below authenticate to Ranger Admin with `-u 
admin:rangerR0cks!` (replace with your Ranger Admin username and password).
+
+---
+
+## How Ozone STS Works
+
+1. A user with permanent S3 credentials calls **AssumeRole** on the STS 
endpoint.
+2. Ozone validates the request signature and asks Ranger whether the caller 
may assume the target role.
+3. If an optional inline session policy (`Policy` parameter) is supplied, 
Ozone converts it to Ranger permissions and actions.
+4. On success, Ozone returns temporary credentials:
+   - **AccessKeyId** — begins with `ASIA`
+   - **SecretAccessKey**
+   - **SessionToken** — opaque, server-side stateless token
+5. Subsequent S3 API calls use the temporary credentials and must include the 
session token in the `x-amz-security-token` header.  If an optional inline 
session policy was supplied in the initial AssumeRole call, the permissions and 
actions for the inline policy will be intersected with the role's permissions 
for authorization.  Otherwise, the role's permissions only will be used for 
authorization.
+
+Temporary credential lifetime is **15 minutes to 12 hours** (900–43,200 
seconds). If `DurationSeconds` is omitted, the token's expiration defaults to 
3600 seconds (1 hour) per the AWS specification.
+
+---
+
+## Enabling STS
+
+### Ozone feature flag
+
+STS is disabled by default. Enable it in `ozone-site.xml`:
+
+```xml
+<property>
+  <name>ozone.s3g.sts.http.enabled</name>
+  <value>true</value>
+  <description>Enable the Ozone S3 Gateway STS endpoint.</description>
+</property>
+```
+
+Restart all Ozone Managers and all S3 Gateways after changing this property to 
have it take effect. Datanodes, SCM, and Recon do not need to be restarted.
+
+:::warning
+
+**Do NOT** enable STS without also enabling the corresponding flag on the 
Ranger side (see "Ranger feature flag" section below). If the Ozone feature 
flag is enabled but not the Ranger feature flag, it is possible for the created 
STS token to have more access than requested. For example, a token requested 
for the `s3:PutObject` action would have access to `s3:PutObject`, 
`s3:PutObjectTagging` and `s3:DeleteObjectTagging` actions, which is a security 
issue. Having both the Ozone and Range [...]
+
+:::
+
+### Additional Configurable Properties
+
+| Property | Default        | Description                                      
               |
+| --- 
|----------------|-----------------------------------------------------------------|
+| `ozone.s3g.sts.http.enabled` | `false`        | Feature flag for the STS 
endpoint                               |
+| `ozone.s3g.sts.http-address` | `0.0.0.0:9880` | HTTP STS bind address        
                                   |
+| `ozone.s3g.sts.https-address` | `0.0.0.0:9881` | HTTPS STS bind address      
                                    |
+| `ozone.s3g.sts.http-bind-host` | `0.0.0.0`      | HTTP bind host (overrides 
hostname portion of `http-address`)   |
+| `ozone.s3g.sts.https-bind-host` | `0.0.0.0`      | HTTPS bind host           
                                      |
+| `ozone.om.sts.token.cleanup.service.interval` | `3h`           | Interval 
for cleaning revoked token entries older than 12 hours |
+| `ozone.om.sts.token.cleanup.service.timeout` | `15m`          | Timeout for 
a cleanup run                                       |
+
+After enabling, the STS endpoint is available at one or more of the following 
URLs, depending on the `ozone.http.policy` value (`HTTP_ONLY`, `HTTPS_ONLY`, or 
`HTTP_AND_HTTPS`) and whether TLS is configured.  By default, the value is 
`HTTP_ONLY`:
+
+```text
+http://<s3g-host>:9880/sts
+https://<s3g-host>:9881/sts
+```
+
+The S3 object API remains on the existing S3G port (typically `9878`).
+
+### Ranger feature flag (action-matches Policy Condition)
+
+To enforce **S3 action-level** restrictions in Ranger policies (required for 
fine-grained STS behavior such as separating `GetObject` from 
`GetObjectTagging` at the same key path), enable the Ozone service-def option 
in Ranger Admin:
+
+```xml
+<!-- ranger-admin-site.xml -->
+<property>
+  <name>ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition</name>
+  <value>true</value>
+</property>
+```
+
+<div style={{overflowX: 'auto'}}>
+
+| Default | When `false`                                                       
                                                                                
                                                                         | When 
`true` |
+| --- 
|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
 --- |
+| `false` | Ranger evaluates only Ozone access types (`read`, `write`, 
`create`, etc.). S3 actions from session policies are not enforced at action 
granularity, neither is it possible to specify granular actions on the role 
itself. | Enables the **Action** (`action-matches`) policy condition. The S3 
Gateway passes the IAM action (for example `PutObject`) to Ranger on each 
request. |
+
+</div>
+
+When this flag is enabled:
+
+- Ranger policies can use the **Action** condition with values such as 
`PutObject`, `GetObject`, `ListBucket`, or wildcards like `Get*`.
+- Inline session policies from AssumeRole carry S3 action constraints that 
Ranger enforces alongside ACL permissions.
+
+Restart Ranger Admin after changing this property so the updated Ozone service 
definition (including the `action-matches` condition) is loaded.
+
+---
+
+## IAM Session Policy to Ranger Permission/Action Mapping
+
+When a caller passes a `Policy` parameter to AssumeRole, Ozone parses the AWS 
IAM session policy JSON and maps each supported S3 action to Ranger permissions 
and actions at **volume**, **bucket**, and **key** levels. Ranger then 
authorizes based on the intersection of the role permissions and actions and 
session policy permissions and actions.
+
+### Supported session policy subset
+
+| IAM element | Supported | Notes                                              
                                                                                
      |
+| --- | --- 
|------------------------------------------------------------------------------------------------------------------------------------------|
+| `Effect` | `Allow` only | `Deny` and other effects are rejected              
                                                                                
      |
+| `Action` | Supported S3 actions and wildcards (`s3:*`, `s3:Get*`, `s3:Put*`, 
`s3:List*`, `s3:Create*`, `s3:Delete*`) | Unknown actions are **silently 
ignored** similar to AWS behavior (AssumeRole still succeeds; credentials 
simply won't grant that action) |
+| `Resource` | `arn:aws:s3:::` prefix or `*` | Other ARN prefixes are rejected 
                                                                                
                         |
+| `Condition` | `StringEquals` or `StringLike` with key `s3:prefix` | Only one 
condition operator per statement; only applies to `s3:ListBucket`               
                                                |
+| Policy size | ≤ 2048 characters | Matches AWS AssumeRole limit               
                                                                                
              |
+
+### S3 action → Ranger permissions and actions
+
+Expand the mapping table below to see, for each S3 action, which Ranger 
permissions and actions are required at each resource level when that action 
appears in a session policy.
+
+Each applicable resource level must also include a matching `action-matches` 
condition for that S3 action (for example `PutObject` at volume, bucket, and 
key for object-scoped actions). If `action-matches` is omitted at a level, the 
ACL permission applies to **every** S3 action that requires that ACL at that 
level. For example, key-level `READ` without `action-matches` authorizes both 
`GetObject` and `GetObjectTagging`. It is **imperative** (for security reasons) 
to specify the action at [...]
+
+<Details summary="Read full S3 action → Ranger mapping table">
+
+<div style={{overflowX: 'auto'}}>
+
+| S3 Action | Scope | Volume | Bucket | Key |
+| --- | --- | --- | --- | --- |
+| `s3:AbortMultipartUpload` | Object | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `AbortMultipartUpload`</span> | 
Permission: `READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`AbortMultipartUpload`</span> | Permission: `WRITE`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `AbortMultipartUpload`</span> |
+| `s3:CreateBucket` | Bucket | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `CreateBucket`</span> | Permission: 
`CREATE`<br/><span style={{whiteSpace: 'nowrap'}}>Action: `CreateBucket`</span> 
| — |
+| `s3:DeleteBucket` | Bucket | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `DeleteBucket`</span> | Permission: 
`DELETE`<br/><span style={{whiteSpace: 'nowrap'}}>Action: `DeleteBucket`</span> 
| — |
+| `s3:DeleteObject` | Object | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `DeleteObject`</span> | Permission: 
`READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: `DeleteObject`</span> | 
Permission: `DELETE`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`DeleteObject`</span> |
+| `s3:DeleteObjectTagging` | Object | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `DeleteObjectTagging`</span> | 
Permission: `READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`DeleteObjectTagging`</span> | Permission: `WRITE`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `DeleteObjectTagging`</span> |
+| `s3:GetBucketAcl` | Bucket | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `GetBucketAcl`</span> | Permission: 
`READ`, `READ_ACL`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`GetBucketAcl`</span> | — |
+| `s3:GetObject` | Object | Permission: `READ`<br/><span style={{whiteSpace: 
'nowrap'}}>Action: `GetObject`</span> | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `GetObject`</span> | Permission: 
`READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: `GetObject`</span> |
+| `s3:GetObjectTagging` | Object | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `GetObjectTagging`</span> | Permission: 
`READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`GetObjectTagging`</span> | Permission: `READ`<br/><span style={{whiteSpace: 
'nowrap'}}>Action: `GetObjectTagging`</span> |
+| `s3:ListAllMyBuckets` | Volume | Permission: `READ`, `LIST`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `ListAllMyBuckets`</span> | — | — |
+| `s3:ListBucket` | Bucket | Permission: `READ`<br/><span style={{whiteSpace: 
'nowrap'}}>Action: `ListBucket`</span> | Permission: `READ`, `LIST`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `ListBucket`</span> | Permission: 
`READ`†<br/><span style={{whiteSpace: 'nowrap'}}>Action: `ListBucket`</span> |
+| `s3:ListBucketMultipartUploads` | Bucket | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `ListBucketMultipartUploads`</span> | 
Permission: `READ`, `LIST`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`ListBucketMultipartUploads`</span> | — |
+| `s3:ListMultipartUploadParts` | Object | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `ListMultipartUploadParts`</span> | 
Permission: `READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`ListMultipartUploadParts`</span> | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `ListMultipartUploadParts`</span> |
+| `s3:PutBucketAcl` | Bucket | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `PutBucketAcl`</span> | Permission: 
`READ`, `READ_ACL`, `WRITE_ACL`<br/><span style={{whiteSpace: 
'nowrap'}}>Action: `PutBucketAcl`</span> | — |
+| `s3:PutObject` | Object | Permission: `READ`<br/><span style={{whiteSpace: 
'nowrap'}}>Action: `PutObject`</span> | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `PutObject`</span> | Permission: 
`CREATE`, `WRITE`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`PutObject`</span> |
+| `s3:PutObjectTagging` | Object | Permission: `READ`<br/><span 
style={{whiteSpace: 'nowrap'}}>Action: `PutObjectTagging`</span> | Permission: 
`READ`<br/><span style={{whiteSpace: 'nowrap'}}>Action: 
`PutObjectTagging`</span> | Permission: `WRITE`<br/><span style={{whiteSpace: 
'nowrap'}}>Action: `PutObjectTagging`</span> |
+
+</div>
+
+† For `s3:ListBucket`, key-level **READ** is granted on the listed prefix (or 
`*` if no `s3:prefix` condition is present).
+
+</Details>
+
+#### Simplifying bucket and key policies with `All`
+
+You can often simplify **bucket** and **key** policies by granting **`All`** 
instead of individual access types such as `write` or `delete`. The 
`action-matches` condition restricts which S3 action the permission applies to, 
so `All` with Action: `PutObject` does not broadly authorize unrelated 
operations at that resource level.
+
+At the **volume** level, prefer **specific** access types such as `read` and 
`list` rather than `All`.  Volume-level permissions apply even when a request 
has no mapped S3 action (similar to bucket-level and key-level permissions), so 
granting `All` on a volume could unintentionally allow destructive operations 
such as volume deletion when the role is used outside the S3 context.
+
+#### Example: IAM permission policy mapped to Ranger policies
+
+Suppose a role should support this IAM permission policy:
+
+```json
+{
+  "Version": "2012-10-17",
+  "Statement": [{
+    "Effect": "Allow",
+    "Action": "s3:GetObject",
+    "Resource": "arn:aws:s3:::reports/*"
+  }]
+}
+```
+
+The corresponding Ranger role needs matching policies at each level:
+
+| Level | Resource | Permission         | Action-matches Policy Condition |
+| --- | --- |--------------------|---------------------------------|
+| Volume | `s3v` | `READ`             | `GetObject`                     |
+| Bucket | `reports` | `READ` (or `All`‡) | `GetObject`                     |
+| Key | `*` (under `reports`) | `READ` (or `All`‡) | `GetObject`               
      |
+
+‡ When using `All` at bucket or key level, the `action-matches: GetObject` 
condition is what prevents unrelated S3 actions from being authorized at that 
level.
+
+Example Ranger policy item for the key level (specific read permission):
+
+```json
+{
+  "accesses": [{ "type": "read", "isAllowed": true }],
+  "roles": ["my-data-reader-role"],
+  "conditions": [{ "type": "action-matches", "values": ["GetObject"] }]
+}
+```
+
+Equivalent simplified key-level policy using `all`:
+
+```json
+{
+  "accesses": [{ "type": "all", "isAllowed": true }],
+  "roles": ["my-data-reader-role"],
+  "conditions": [{ "type": "action-matches", "values": ["GetObject"] }]
+}
+```
+
+:::warning
+
+Without `action-matches` on the volume, bucket and key policies, the same 
key-level `read` grant would also authorize `GetObjectTagging` and any other 
read-scoped S3 action at that path.
+
+:::
+
+#### `s3:prefix` condition (ListBucket only)
+
+If an IAM session policy or role permission policy statement includes a 
Condition, **only `s3:ListBucket`** from that statement takes effect (similar 
to AWS behavior).
+
+| Operator | Behavior |
+| --- | --- |
+| `StringEquals` | Exact prefix match; wildcard values in `s3:prefix` are 
ignored |
+| `StringLike` | Prefix pattern match; supports `*` wildcards |
+
+The caller must supply a matching `prefix` (and optionally `delimiter`) on 
ListObjects/ListObjectsV2 or access is denied.
+
+---
+
+## Ranger Policy Setup
+
+This section walks through how to configure Ranger via example. Replace 
`RANGER_URL`, `RANGER_SERVICE`, and the `-u` credentials before running the 
commands (see Prerequisites section above).
+
+### 1. Create the Ranger user (service principal)
+
+The `/service/xusers/secure/users` endpoint is required so `userPermList` can 
be set; without it the user cannot be added to Ranger policies.
+
+```shell
+curl --silent --show-error --location -u admin:rangerR0cks! \
+  --request POST \
+  --header "Content-Type: application/json" \
+  --header "accept: application/json" \
+  --data '{
+    "loginId": "my-service-user",
+    "name": "my-service-user",
+    "password": "Password123",
+    "firstName": "My Service",
+    "lastName": "User",
+    "emailAddress": "[email protected]",
+    "userRoleList": ["ROLE_USER"],
+    "userPermList": [
+      { "moduleId": 1, "isAllowed": 1 },
+      { "moduleId": 3, "isAllowed": 1 },
+      { "moduleId": 7, "isAllowed": 1 }
+    ]
+  }' \
+  "${RANGER_URL}/service/xusers/secure/users"
+```
+
+### 2. Create the Ranger role
+
+```shell
+curl --silent --show-error --location -u admin:rangerR0cks! \
+  --request POST \
+  --header "Content-Type: application/json" \
+  --header "accept: application/json" \
+  --data '{
+    "name": "my-data-read-write-role",
+    "description": "Read-write data access role for STS"
+  }' \
+  "${RANGER_URL}/service/roles/roles"
+```
+
+### 3. Assume role policy (caller → role)
+
+Grant the service user permission to call AssumeRole for the role:
+
+```shell
+curl --silent --show-error --location -u admin:rangerR0cks! \
+  --request POST \
+  --header "Content-Type: application/json" \
+  --header "accept: application/json" \
+  --data '{
+    "isEnabled": true,
+    "service": "'"${RANGER_SERVICE}"'",
+    "name": "my-data-read-write-role assume role policy",
+    "policyType": 0,
+    "policyPriority": 0,
+    "isAuditEnabled": true,
+    "resources": {
+      "role": {
+        "values": ["my-data-read-write-role"],
+        "isExcludes": false,
+        "isRecursive": false
+      }
+    },
+    "policyItems": [{
+      "accesses": [{ "type": "assume_role", "isAllowed": true }],
+      "users": ["my-service-user"],
+      "delegateAdmin": false
+    }],
+    "serviceType": "ozone",
+    "isDenyAllElse": false
+  }' \
+  "${RANGER_URL}/service/public/v2/api/policy"
+```
+
+### 4. Role resource policies (what the role can do)
+
+Let's use the following IAM permission policy and make equivalent Ranger 
policies as an example:
+
+```json
+{
+  "Version": "2012-10-17",
+  "Statement": [{
+    "Effect": "Allow",
+    "Action": ["s3:GetObject","s3:PutObject"],
+    "Resource": "arn:aws:s3:::reports/*"
+  }]
+}
+```
+
+**Volume** (prefer specific access types — not `All`):
+
+```shell
+curl --silent --show-error --location -u admin:rangerR0cks! \
+  --request POST \
+  --header "Content-Type: application/json" \
+  --header "accept: application/json" \
+  --data '{
+    "isEnabled": true,
+    "service": "'"${RANGER_SERVICE}"'",
+    "name": "read/write volume access",
+    "policyType": 0,
+    "policyPriority": 0,
+    "isAuditEnabled": true,
+    "resources": {
+      "volume": {
+        "values": ["s3v"],
+        "isExcludes": false,
+        "isRecursive": false
+      }
+    },
+    "policyItems": [{
+      "accesses": [{ "type": "read", "isAllowed": true }],
+      "roles": ["my-data-read-write-role"],
+      "conditions": [{ "type": "action-matches", "values": ["GetObject", 
"PutObject"] }],
+      "delegateAdmin": false
+    }],
+    "serviceType": "ozone",
+    "isDenyAllElse": false
+  }' \
+  "${RANGER_URL}/service/public/v2/api/policy"
+```
+
+**Bucket** (`READ` or `All` both work when paired with `action-matches`):
+
+```shell
+curl --silent --show-error --location -u admin:rangerR0cks! \
+  --request POST \
+  --header "Content-Type: application/json" \
+  --header "accept: application/json" \
+  --data '{
+    "isEnabled": true,
+    "service": "'"${RANGER_SERVICE}"'",
+    "name": "read/write bucket access",
+    "policyType": 0,
+    "policyPriority": 0,
+    "isAuditEnabled": true,
+    "resources": {
+      "volume": {
+        "values": ["s3v"],
+        "isExcludes": false,
+        "isRecursive": false
+      },
+      "bucket": {
+        "values": ["reports"],
+        "isExcludes": false,
+        "isRecursive": false
+      }
+    },
+    "policyItems": [{
+      "accesses": [{ "type": "all", "isAllowed": true }],
+      "roles": ["my-data-read-write-role"],
+      "conditions": [{ "type": "action-matches", "values": ["GetObject", 
"PutObject"] }],
+      "delegateAdmin": false
+    }],
+    "serviceType": "ozone",
+    "isDenyAllElse": false
+  }' \
+  "${RANGER_URL}/service/public/v2/api/policy"
+```
+
+**Key** (`READ, CREATE, WRITE` or `All` both work when paired with 
`action-matches`):
+
+```shell
+curl --silent --show-error --location -u admin:rangerR0cks! \
+  --request POST \
+  --header "Content-Type: application/json" \
+  --header "accept: application/json" \
+  --data '{
+    "isEnabled": true,
+    "service": "'"${RANGER_SERVICE}"'",
+    "name": "read/write key access",
+    "policyType": 0,
+    "policyPriority": 0,
+    "isAuditEnabled": true,
+    "resources": {
+      "volume": {
+        "values": ["s3v"],
+        "isExcludes": false,
+        "isRecursive": false
+      },
+      "bucket": {
+        "values": ["reports"],
+        "isExcludes": false,
+        "isRecursive": false
+      },
+      "key": {
+        "values": ["*"],
+        "isExcludes": false,
+        "isRecursive": true
+      }
+    },
+    "policyItems": [{
+      "accesses": [{ "type": "all", "isAllowed": true }],
+      "roles": ["my-data-read-write-role"],
+      "conditions": [{ "type": "action-matches", "values": ["GetObject", 
"PutObject"] }],
+      "delegateAdmin": false
+    }],
+    "serviceType": "ozone",
+    "isDenyAllElse": false
+  }' \
+  "${RANGER_URL}/service/public/v2/api/policy"
+```
+
+---
+
+## Sample Ozone STS Usage
+
+### Step 1: Obtain permanent credentials
+
+```shell
+kinit my-service-user
+ozone s3 getsecret
+export PERM_AWS_ACCESS_KEY_ID=<awsAccessKey from output>
+export PERM_AWS_SECRET_ACCESS_KEY=<awsSecret from output>
+export AWS_ACCESS_KEY_ID=$PERM_AWS_ACCESS_KEY_ID
+export AWS_SECRET_ACCESS_KEY=$PERM_AWS_SECRET_ACCESS_KEY
+export AWS_DEFAULT_REGION=<region such as us-east-1>
+```
+
+### Step 2: Create the reports bucket in Ozone and upload sample object
+
+`my-service-user` does not have permission to read the `s3v` volume or create 
buckets. Switch to a Kerberos identity with permission to read the s3v volume 
and create `report` bucket (you may need to create this user and assign it the 
proper permissions in Ranger policies), create the `reports` bucket referenced 
in the Ranger policies above, upload a sample object for `GetObject` tests in 
later steps, then switch back to `my-service-user`.
+
+```shell
+kinit <userWithPermissionToReadS3VVolumeAndCreateReportsBucket>
+ozone sh bucket create /s3v/reports
+printf 'sample data for GetObject test\n' > /tmp/file.parquet
+ozone sh key put /s3v/reports/data/file.parquet /tmp/file.parquet
+kinit my-service-user
+```
+
+### Step 3: Assume a role (full role permissions)
+
+AssumeRole must be invoked with the caller's permanent credentials 
(`PERM_AWS_*` from Step 1):
+
+```shell
+CREDS=$(AWS_ACCESS_KEY_ID=$PERM_AWS_ACCESS_KEY_ID \
+  AWS_SECRET_ACCESS_KEY=$PERM_AWS_SECRET_ACCESS_KEY \
+  AWS_SESSION_TOKEN= \
+  aws sts assume-role \
+  --endpoint-url http://<s3g-host>:9880/sts \
+  --role-arn arn:aws:iam::123456789012:role/my-data-read-write-role \
+  --role-session-name catalog-session \
+  --duration-seconds 3600 \
+  --output json)
+
+export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r .Credentials.AccessKeyId)
+export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r 
.Credentials.SecretAccessKey)
+export AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r .Credentials.SessionToken)
+export AWS_ENDPOINT_URL_S3=http://<s3g-host>:9878
+```
+
+Remember to replace `<s3g-host>` with the correct value for your server (for 
example localhost).  The temporary access key ID in the response starts with 
`ASIA`. `Expiration` in the response reflects the requested duration.
+
+### Step 4: Use temporary credentials for S3
+
+```shell
+# Read allowed
+aws s3api get-object \
+  --endpoint-url "$AWS_ENDPOINT_URL_S3" \
+  --bucket reports --key data/file.parquet /tmp/file.parquet
+
+# Write allowed as well
+printf 'new data for PutObject test\n' > /tmp/new.parquet
+aws s3api put-object \
+  --endpoint-url "$AWS_ENDPOINT_URL_S3" \
+  --bucket reports --key data/new.parquet --body /tmp/new.parquet
+```
+
+### Step 5: Assume a role with an inline session policy
+
+In this example, even though the role has read/write permissions in Ranger, 
use an inline session policy to limit authorization to read-only via GetObject.
+
+AssumeRole must be invoked with the caller's **permanent** credentials. After 
Step 3, the shell holds temporary role credentials in `AWS_*`.  Use the saved 
`PERM_AWS_*` values and clear `AWS_SESSION_TOKEN` for this call (same pattern 
as Step 3):
+
+```shell
+SESSION_POLICY='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::reports/*"}]}'
+
+CREDS=$(AWS_ACCESS_KEY_ID=$PERM_AWS_ACCESS_KEY_ID \
+  AWS_SECRET_ACCESS_KEY=$PERM_AWS_SECRET_ACCESS_KEY \
+  AWS_SESSION_TOKEN= \
+  aws sts assume-role \
+  --endpoint-url http://<s3g-host>:9880/sts \
+  --role-arn arn:aws:iam::123456789012:role/my-data-read-write-role \
+  --role-session-name scoped-read \
+  --policy "$SESSION_POLICY" \
+  --output json)
+
+export AWS_ACCESS_KEY_ID=$(echo "$CREDS" | jq -r .Credentials.AccessKeyId)
+export AWS_SECRET_ACCESS_KEY=$(echo "$CREDS" | jq -r 
.Credentials.SecretAccessKey)
+export AWS_SESSION_TOKEN=$(echo "$CREDS" | jq -r .Credentials.SessionToken)
+export AWS_ENDPOINT_URL_S3=http://<s3g-host>:9878
+```
+
+Remember to replace `<s3g-host>` with the correct value for your server (for 
example localhost).
+
+### Step 6: Use temporary credentials for S3
+
+```shell
+# Read allowed
+aws s3api get-object \
+  --endpoint-url "$AWS_ENDPOINT_URL_S3" \
+  --bucket reports --key data/file.parquet /tmp/file.parquet
+
+# Write denied since session policy only allowed GetObject
+aws s3api put-object \
+  --endpoint-url "$AWS_ENDPOINT_URL_S3" \
+  --bucket reports --key data/new.parquet --body /tmp/new.parquet
+# → AccessDenied
+```
+
+### Revoke a session token
+
+Only the token creator or an S3/tenant admin may revoke:
+
+```shell
+ozone s3 revokeststoken -t "$(echo "$CREDS" | jq -r 
.Credentials.SessionToken)" -y
+```
+
+Revoking a user's permanent secret also invalidates all outstanding STS tokens 
for that user:
+
+```shell
+ozone s3 revokesecret -u my-service-user -y
+```
+
+---
+
+## Considerations
+
+- **Multi-tenancy**: In [S3 multi-tenant](./multi-tenancy/overview) 
deployments, the volume in resource policies may differ from `s3v`.
+- **Ranger policy refresh**: Allow time for policy cache refresh (up to 30 
seconds) after creating/modifying resource policies before calling AssumeRole.
+
+---
+
+## Further Reading
+
+- [AWS STS Design for Ozone 
S3](https://github.com/apache/ozone/blob/master/hadoop-hdds/docs/content/design/ozone-sts.md)
 — architecture, session token format, and design rationale
+- [Configuring Apache 
Ranger](../../../administrator-guide/configuration/security/ranger) — base 
Ranger/Ozone integration
+- [Securing S3](../../../user-guide/client-interfaces/s3/securing-s3) — 
Kerberos, S3 secrets, and S3 Gateway security
diff --git a/docs/05-administrator-guide/03-operations/07-s3/README.mdx 
b/docs/05-administrator-guide/03-operations/07-s3/README.mdx
new file mode 100644
index 0000000000..86f8914300
--- /dev/null
+++ b/docs/05-administrator-guide/03-operations/07-s3/README.mdx
@@ -0,0 +1,11 @@
+---
+sidebar_label: S3
+---
+
+# S3
+
+import DocCardList from '@theme/DocCardList';
+
+This section documents S3-related administrator operations in Ozone.
+
+<DocCardList/>


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to