Ozone S3 Security Token Service (STS)
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:
-
Secure Ozone cluster with Kerberos and Ranger enabled.
-
Ranger Ozone plugin installed on OM/S3G with
RangerOzoneAuthorizerconfigured (see Configuring Apache Ranger). -
Permanent S3 credentials for the calling user (i.e. service principal):
kinit my-service-userozone s3 getsecret# awsAccessKey=...# awsSecret=... -
Ozone volume(s), bucket(s) and key(s) created as needed for the resources the STS tokens would need to access
-
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:
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
- A user with permanent S3 credentials calls AssumeRole on the STS endpoint.
- Ozone validates the request signature and asks Ranger whether the caller may assume the target role.
- If an optional inline session policy (
Policyparameter) is supplied, Ozone converts it to Ranger permissions and actions. - On success, Ozone returns temporary credentials:
- AccessKeyId — begins with
ASIA - SecretAccessKey
- SessionToken — opaque, server-side stateless token
- AccessKeyId — begins with
- Subsequent S3 API calls use the temporary credentials and must include the session token in the
x-amz-security-tokenheader. 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:
<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.
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 Ranger feature flags set to true prevents these scenarios from happening provided the Ranger policies are also set up correctly.
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:
http://<s3g-host>:9880
https://<s3g-host>:9881
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:
<!-- ranger-admin-site.xml -->
<property>
<name>ranger.servicedef.ozone.enableActionMatcherInPoliciesCondition</name>
<value>true</value>
</property>
| 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. |
When this flag is enabled:
- Ranger policies can use the Action condition with values such as
PutObject,GetObject,ListBucket, or wildcards likeGet*. - 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 every applicable level.
Read full S3 action → Ranger mapping table
| S3 Action | Scope | Volume | Bucket | Key |
|---|---|---|---|---|
s3:AbortMultipartUpload | Object | Permission: READAction: AbortMultipartUpload | Permission: READAction: AbortMultipartUpload | Permission: WRITEAction: AbortMultipartUpload |
s3:CreateBucket | Bucket | Permission: READAction: CreateBucket | Permission: CREATEAction: CreateBucket | — |
s3:DeleteBucket | Bucket | Permission: READAction: DeleteBucket | Permission: DELETEAction: DeleteBucket | — |
s3:DeleteObject | Object | Permission: READAction: DeleteObject | Permission: READAction: DeleteObject | Permission: DELETEAction: DeleteObject |
s3:DeleteObjectTagging | Object | Permission: READAction: DeleteObjectTagging | Permission: READAction: DeleteObjectTagging | Permission: WRITEAction: DeleteObjectTagging |
s3:GetBucketAcl | Bucket | Permission: READAction: GetBucketAcl | Permission: READ, READ_ACLAction: GetBucketAcl | — |
s3:GetObject | Object | Permission: READAction: GetObject | Permission: READAction: GetObject | Permission: READAction: GetObject |
s3:GetObjectTagging | Object | Permission: READAction: GetObjectTagging | Permission: READAction: GetObjectTagging | Permission: READAction: GetObjectTagging |
s3:ListAllMyBuckets | Volume | Permission: READ, LISTAction: ListAllMyBuckets | — | — |
s3:ListBucket | Bucket | Permission: READAction: ListBucket | Permission: READ, LISTAction: ListBucket | Permission: READ†Action: ListBucket |
s3:ListBucketMultipartUploads | Bucket | Permission: READAction: ListBucketMultipartUploads | Permission: READ, LISTAction: ListBucketMultipartUploads | — |
s3:ListMultipartUploadParts | Object | Permission: READAction: ListMultipartUploadParts | Permission: READAction: ListMultipartUploadParts | Permission: READAction: ListMultipartUploadParts |
s3:PutBucketAcl | Bucket | Permission: READAction: PutBucketAcl | Permission: READ, READ_ACL, WRITE_ACLAction: PutBucketAcl | — |
s3:PutObject | Object | Permission: READAction: PutObject | Permission: READAction: PutObject | Permission: CREATE, WRITEAction: PutObject |
s3:PutObjectTagging | Object | Permission: READAction: PutObjectTagging | Permission: READAction: PutObjectTagging | Permission: WRITEAction: PutObjectTagging |
† For s3:ListBucket, key-level READ is granted on the listed prefix (or * if no s3:prefix condition is present).
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:
{
"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):
{
"accesses": [{ "type": "read", "isAllowed": true }],
"roles": ["my-data-reader-role"],
"conditions": [{ "type": "action-matches", "values": ["GetObject"] }]
}
Equivalent simplified key-level policy using all:
{
"accesses": [{ "type": "all", "isAllowed": true }],
"roles": ["my-data-reader-role"],
"conditions": [{ "type": "action-matches", "values": ["GetObject"] }]
}
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.
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": "my-service-user@example.com",
"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
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:
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:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject","s3:PutObject"],
"Resource": "arn:aws:s3:::reports/*"
}]
}
Volume (prefer specific access types — not All):
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):
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):
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
Complete Ranger Policy Setup if you have not already.
Step 1: Obtain permanent credentials
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 reports 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.
kinit <userWithPermissionToReadS3VVolumeAndCreateReportsBucket>
ozone sh bucket create /s3v/reports
printf 'sample data for GetObject test\n' > /tmp/file.txt
ozone sh key put /s3v/reports/data/file.txt /tmp/file.txt
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):
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 \
--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
# Read allowed
aws s3api get-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports --key data/file.txt /tmp/file.txt
# Write allowed as well
printf 'new data for PutObject test\n' > /tmp/new.txt
aws s3api put-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports --key data/new.txt --body /tmp/new.txt
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):
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 \
--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
# Read allowed
aws s3api get-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports --key data/file.txt /tmp/file.txt
# Write denied since session policy only allowed GetObject
aws s3api put-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports --key data/new.txt --body /tmp/new.txt
# → AccessDenied
Revoking session tokens
Ozone STS provides the ability to revoke STS tokens via command-line interface, such that all tokens created by the user before the time the command was issued will be revoked. STS tokens created after the command is run will still work fine. Only the user identity corresponding to originalAccessKeyId or an S3/tenant admin may revoke:
ozone s3 revokeststoken -o "$PERM_AWS_ACCESS_KEY_ID" -y
Revoking a user's permanent secret also invalidates all outstanding STS tokens for that user (irrespective of cutoff) and no new tokens can be created by that user:
ozone s3 revokesecret -u "$PERM_AWS_ACCESS_KEY_ID" -y
Sample Ozone STS Usage - Linked Buckets
Ozone allows buckets to be linked. There can be a source bucket S, that is linked to bucket A, which is linked to bucket B, and so on. If you need to use a linked bucket when assuming the role, ensure the Ranger policies for the role have the proper permissions for each link in the chain as well as the source bucket. As an example, suppose we want to assume role on bucket B. The role needs read access to bucket B, read access to bucket A, and the requisite access for bucket S (such as read on keys for GetObject, create and write on keys for PutObject, etc.). The role also must have at least read access to the volume(s) where these buckets live as well.
For this example, we'll use reports bucket as the source bucket, reports-link-1 as bucket A and reports-link-2 as bucket B.
Step 1: Configure Ranger policies
Complete Ranger Policy Setup if you have not already. That section covers the Ranger user, role, assume_role policy, and volume/bucket/key policies for the source reports bucket.
Because S3 clients access reports-link-2, Ranger must also authorize each link in the resolution chain. Add a bucket policy for each link bucket (reports-link-1 and reports-link-2) with at least read access and matching action-matches conditions for the S3 actions the role will perform:
Bucket reports-link-1 (link to source bucket reports):
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 reports-link-1 bucket access",
"policyType": 0,
"policyPriority": 0,
"isAuditEnabled": true,
"resources": {
"volume": {
"values": ["s3v"],
"isExcludes": false,
"isRecursive": false
},
"bucket": {
"values": ["reports-link-1"],
"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 reports-link-2 (link to reports-link-1; this is the bucket name used in S3 API calls):
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 reports-link-2 bucket access",
"policyType": 0,
"policyPriority": 0,
"isAuditEnabled": true,
"resources": {
"volume": {
"values": ["s3v"],
"isExcludes": false,
"isRecursive": false
},
"bucket": {
"values": ["reports-link-2"],
"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"
Key-level read, create, and write permissions remain on the source bucket reports only; link buckets do not store keys.
Allow time for Ranger policy cache refresh (up to 30 seconds) before calling AssumeRole.
Step 2: Obtain permanent credentials
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 3: Create the source bucket, bucket links, and 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 buckets (you may need to create this user and assign it the proper permissions in Ranger policies), then:
- Create the source bucket
reportsand upload a sample object. - Create bucket links so
reports-link-1points toreports, andreports-link-2points toreports-link-1.
kinit <userWithPermissionToReadS3VVolumeAndCreateReportsBucket>
ozone sh bucket create /s3v/reports
printf 'sample data for GetObject test\n' > /tmp/file.txt
ozone sh key put /s3v/reports/data/file.txt /tmp/file.txt
ozone sh bucket link /s3v/reports /s3v/reports-link-1
ozone sh bucket link /s3v/reports-link-1 /s3v/reports-link-2
kinit my-service-user
For background on bucket links, see Bucket Links.
Step 4: Assume a role (full role permissions)
AssumeRole must be invoked with the caller's permanent credentials (PERM_AWS_* from Step 2):
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 \
--role-arn arn:aws:iam::123456789012:role/my-data-read-write-role \
--role-session-name linked-bucket-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).
Step 5: Use temporary credentials against the linked bucket
Access data through bucket reports-link-2. Ozone resolves the link chain to the source bucket reports transparently:
# Read allowed (data stored in /s3v/reports/data/file.txt)
aws s3api get-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports-link-2 --key data/file.txt /tmp/file.txt
# Write allowed as well (object created in source bucket reports)
printf 'new data for PutObject test\n' > /tmp/new.txt
aws s3api put-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports-link-2 --key data/new.txt --body /tmp/new.txt
If Ranger policies omit read access on any link bucket in the chain, or omit the requisite key permissions on the source bucket reports, these calls return AccessDenied even though the S3 client targets reports-link-2.
Step 6: Assume a role with an inline session policy (optional)
To scope temporary credentials to the linked bucket only, pass an inline session policy whose Resource references reports-link-2. AssumeRole must be invoked with the caller's permanent credentials:
SESSION_POLICY='{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":"s3:GetObject","Resource":"arn:aws:s3:::reports-link-2/*"}]}'
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 \
--role-arn arn:aws:iam::123456789012:role/my-data-read-write-role \
--role-session-name linked-bucket-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
# Read allowed via reports-link-2
aws s3api get-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports-link-2 --key data/file.txt /tmp/file.txt
# Write denied since session policy only allowed GetObject
aws s3api put-object \
--endpoint-url "$AWS_ENDPOINT_URL_S3" \
--bucket reports-link-2 --key data/new.txt --body /tmp/new.txt
# → AccessDenied
Considerations
- Multi-tenancy: In S3 multi-tenant 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 — architecture, session token format, and design rationale
- Configuring Apache Ranger — base Ranger/Ozone integration
- Securing S3 — Kerberos, S3 secrets, and S3 Gateway security