Skip to content

HDDS-15137. [STS] Ensure each S3 API has an associated S3 Action#10197

Merged
ChenSammi merged 7 commits into
apache:HDDS-13323-stsfrom
fmorg-git:HDDS-15137
Jun 12, 2026
Merged

HDDS-15137. [STS] Ensure each S3 API has an associated S3 Action#10197
ChenSammi merged 7 commits into
apache:HDDS-13323-stsfrom
fmorg-git:HDDS-15137

Conversation

@fmorg-git

@fmorg-git fmorg-git commented May 5, 2026

Copy link
Copy Markdown
Contributor

Please describe your PR in detail:

  • In order to restrict STS tokens on a fine-grained basis by action, each S3 api needs to be associated with an S3 action. For example, both HeadObject and GetObject S3 apis are associated with "s3:GetObject" action. The actions will not have the s3: prefix when used internally in Ozone and Ranger.

Most apis have a 1-1 mapping with api and action like the HeadObject and GetObject apis mentioned previously. In order to avoid having to update multiple call sites, an S3GActionIamMapper is introduced to leverage the already existing api audit functionality and map api names to actions in a thread local (S3Authentication.getS3Action()). Then in OmMetadataReader.checkAcls(), the maybeAttachS3ActionFromThreadLocal function is used to read the thread local and set the s3Action in the RequestContext if it exists.

However, CopyObject and UploadPartCopy are special cases. There is no "s3:CopyObject" nor "s3:UploadPartCopy" action, but rather they both require "s3:GetObject" action on the source object and "s3:PutObject" action on the destination object. For these special cases, a helper method runWithS3ActionString is introduced in EndpointBase.java so the proper permissions can be checked on the source and on the destination by temporarily overriding the value in the thread local while the operation is invoked, then setting it back to the previous value when done.

Additionally, this ticket contains additional fixes found after testing with these new changes. For example, since we now have fine-grained actions, we no longer need to distinguish listing files permission vs download files permission with LIST acl vs READ acl. Both apis can use READ acl.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15137

How was this patch tested?

unit tests, smoke tests

@fmorg-git fmorg-git marked this pull request as ready for review May 27, 2026 17:06
@fmorg-git

Copy link
Copy Markdown
Contributor Author

hi @ChenSammi @sodonnel - this PR has been rebased and is ready for review. Thanks!

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@ChenSammi ChenSammi requested a review from Copilot June 10, 2026 08:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 16 changed files in this pull request and generated 3 comments.

Comment on lines +1120 to +1121
final OzoneKeyDetails destKeyDetails = getClientProtocol().getKeyDetails(
volume.getName(), destBucket, destkey);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the concern is correct, the suggested fix is incorrect. It is fixed in the follow-on PR https://github.com/apache/ozone/pull/10203/changes by removing the extra destKeyDetails call (which will help performance as well).

Comment on lines +697 to +703
final S3Authentication s3Authentication = OzoneManager.getS3Auth();
if (s3Authentication == null || !s3Authentication.hasS3Action()) {
return context;
}
return context.toBuilder()
.setS3Action(s3Authentication.getS3Action())
.build();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated - a223e5c

Comment on lines +262 to 264
}

protected OzoneVolume getVolume() throws IOException {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated - 5868614

}

message CommitKeyResponse {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is irrelevant, better reverted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated - 5868614

}
}
]
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is irrelevant, better reverted.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated - 5868614

* @param context the original RequestContext
* @return RequestContext as before or with s3 action embedded
*/
private RequestContext maybeAttachS3ActionFromThreadLocal(RequestContext context) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can maybeAttachS3ActionFromThreadLocal and maybeAttachSessionPolicyFromThreadLocal merge into one function?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, can we move setS3Action and setSessionPolicy to where context is created?

@ChenSammi ChenSammi Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot found where context.getS3Action() is used. Is this a complete patch, or there will be following patch which requires this get function?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated to combine the maybeAttach methods and move the setters to where the context is created: a223e5c

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cannot found where context.getS3Action() is used. Is this a complete patch, or there will be following patch which requires this get function?

This is used in theRangerOzoneAuthorizer to help authorize against the specific action. See apache/ranger#995 and in particular https://github.com/fmorg-git/ranger/blob/e6327b6bd039ef52d8c82e203be4d8c034214f09/plugin-ozone/src/main/java/org/apache/ranger/authorization/ozone/authorizer/RangerOzoneAuthorizer.java#L168-L173

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see.

@fmorg-git fmorg-git Jun 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, can we move setS3Action and setSessionPolicy to where context is created?

originally, I implemented this suggestion but this broke some of my unit tests in TestOMMetadataReader. Now I remember why I did it the way I did, is because there are many sites where the RequestContext is created but they all funnel down to this method in OmMetadataReader:

public boolean checkAcls(OzoneObj obj, RequestContext context,
      boolean throwIfPermissionDenied)

So it's safer to have it in the one central location that everyone uses, because if we try to rely on adding to all the sites where RequestContext is created, if a new overloaded call site is added in future, someone would need to remember to update that one as well or else there would be a bug until it is realized, which is brittle. The second issue is that someone might call this method directly like the unit test does, and if we don't set the values here, it would break, just like the unit test. So if we need to set the values in this method anyway, we might as well just set it once. I understand it is an extra object allocation, but at least now the two maybeAttach* methods are combined into one, so it's one less allocation and the code is more maintainable with the one central location.

long putLength;
try (OzoneOutputStream ozoneOutputStream = getClientProtocol()
final long putLength;
final OzoneOutputStream ozoneOutputStream = getClientProtocol()

@ChenSammi ChenSammi Jun 10, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

runWithS3ActionString("PutObject" is missed here. ozoneOutputStream is not closed

@fmorg-git fmorg-git Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, here is the reason "PutObject" is not missed here. In this else branch of the code, the request action is set to S3GAction.CREATE_MULTIPART_KEY and this has a mapping in S3GActionIamMapper to "PutObject", so it's covered. In the if branch of the code, the request action is set to S3GAction.CREATE_MULTIPART_KEY_BY_COPY which is mapped to null in S3GActionIamMapper (by design, since it needs "GetObject" on the source and "PutObject" on the destination.)

Also, the ozoneOutputStream is closed on line 920 in the try-with-resources block.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a clarification comment about the runWithS3ActionString - e344aae

final long finalLength = length;
final long bytesToCopy = length;
omMultipartCommitUploadPartInfo = runWithS3ActionString("PutObject", () -> {
final OzoneOutputStream ozoneOutputStream = getClientProtocol().createMultipartKey(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ozoneOutputStream is not closed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ozoneOutputStream is closed on line 898 in the try-with-resources block

Fabian Morgan added 2 commits June 11, 2026 14:24
…icy and s3Action and set them at the call sites

Generated-By: Claude Code with manual updates by me
@fmorg-git fmorg-git requested a review from ChenSammi June 11, 2026 23:53
@fmorg-git fmorg-git requested a review from ChenSammi June 12, 2026 04:54
@@ -628,8 +628,12 @@ public boolean checkAcls(ResourceType resType, StoreType storeType,
public boolean checkAcls(OzoneObj obj, RequestContext context,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can pass RequestContext.Builder in instead of RequestContext here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep, that's a much better idea. Updated - 77cfc5e

@ChenSammi ChenSammi merged commit fd0ae5c into apache:HDDS-13323-sts Jun 12, 2026
47 checks passed
@fmorg-git

Copy link
Copy Markdown
Contributor Author

Thanks for the review and merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants