HDDS-16110. [STS] Update key in sts revocation table - #11095
Conversation
|
hi @ChenSammi @chihsuan - please review this one instead. Thanks! |
…to audit log" This reverts commit d3c9ea5.
|
|
||
| // Add tempAccessKeyId to the log so it can be determined which permanent user created the tempAccessKeyId | ||
| auditMap.put("tempAccessKeyId", tempAccessKeyId); | ||
| auditMap.put(OzoneConsts.S3_STS_TEMP_ACCESS_KEY_ID, tempAccessKeyId); |
There was a problem hiding this comment.
Can we log the original access key too in audit log?
There was a problem hiding this comment.
So the user field in the om audit log is essentially the originalAccessKeyId. A sample om audit log entry is below:
2026-08-23 22:23:09,651 | INFO | OMAudit | user=svc-iceberg-rest-catalog/s3g@EXAMPLE.COM | ip=172.25.0.120 | op=S3_ASSUME_ROLE {"duration":"900","Transaction":"283","isPolicyIncluded":"Y","roleArn":"arn:aws:iam::123456789012:role/sts-temp-bucket-access","requestId":"c2ebe586-df7b-41d9-a964-811312c4a199","roleSessionName":"sts-session-name","action":"AssumeRole","tempAccessKeyId":"ASIA3X2X4JL0GBX0595DU2MU"} | ret=SUCCESS |
Just confirming if we still need to add a separate originalAccessKeyId parameter?
There was a problem hiding this comment.
We can use user field. Not must to add a separate originalAccessKeyId, unless we want to make the op=S3_ASSUME_ROLE completely include all the important fields as a whole.
|
|
||
| return omRequest; | ||
| if (!ozoneManager.getS3SecretManager().hasS3Secret(originalAccessKeyId)) { | ||
| throw new OMException("originalAccessKeyId does not exist: " + originalAccessKeyId, INVALID_REQUEST); |
There was a problem hiding this comment.
We can return ACCESS_ID_NOT_FOUND here.
| This request will be used internally by OM to replicate the revocation cutoff captured by the leader | ||
| across the OMs in HA mode. | ||
| */ | ||
| message UpdateRevokeSTSTokenRequest { |
| final OzoneManagerProtocolProtos.RevokeSTSTokenRequest revokeReq = | ||
| omRequest.getRevokeSTSTokenRequest(); | ||
| final RevokeSTSTokenRequest revokeReq = omRequest.getRevokeSTSTokenRequest(); | ||
| validateRevokeRequestFields(revokeReq); |
There was a problem hiding this comment.
Maybe we don't need to valid the access key ID format anymore here, given that exact DB record match search is performed later.
There was a problem hiding this comment.
updated - 4e50ca5
However, because we are now reusing RevokeSTSTokenRequest for the update, we need to validate that the client did not send revocationTimeMillis
| // Audit log | ||
| final Map<String, String> auditMap = new HashMap<>(); | ||
| final OzoneManagerProtocolProtos.UserInfo userInfo = getOmRequest().getUserInfo(); | ||
| auditMap.put(OzoneConsts.S3_REVOKESTSTOKEN_USER, userInfo.getUserName()); |
There was a problem hiding this comment.
In the s3 secret case, S3RevokeSecretUser is the user which is been revoked. In STS case, it's
the S3_STS_ORIGINAL_ACCESS_KEY_ID.
So maybe we can combine to show
auditMap.put(OzoneConsts.S3_REVOKESTSTOKEN_USER, originalAccessKeyId);
, and move the statement ahead to the try scope, so no originalAccessKeyId null check.
Besides, we don't need to log the userInfo explicitly, it's covered in buildAuditMessage().
| token.decodeFromUrlString(encodedToken); | ||
| return token; | ||
| } catch (IOException e) { | ||
| } catch (IOException | RuntimeException e) { |
There was a problem hiding this comment.
This RuntimeException seems not required.
There was a problem hiding this comment.
so actually, this is very subtle and required. The catching of this RuntimeException is what I was referring to for this fix in the PR description As a side effect of testing this change, it was noticed certain inputs could cause the token parsing to fail that were not already covered, so this PR addresses that as well.
Here is explanation:
Token.decodeFromUrlString() only declares IOException, but Hadoop’s deserialization can throw unchecked exceptions for malformed tokens — e.g. NegativeArraySizeException when a decoded length is negative.
We want all decode failures mapped to SecretManager.InvalidToken → OMException(INVALID_TOKEN), so that’s why RuntimeException is caught here. testConstructValidateAndDecryptSTSTokenRuntimeDecodeFailure in unit tests and the not-a-valid-token case in Tampered STS Token Service, Policy, or Signature Must Fail smoke test cover this.
| // serialized there. | ||
| final STSTokenIdentifier identifier = new STSTokenIdentifier( | ||
| tempAccessKeyId, originalAccessKeyId, roleArn, expiration, secretAccessKey, sessionPolicy, encryptionKey); | ||
| final STSTokenIdentifier identifier = new STSTokenIdentifier(STSTokenIdentifier.Params.newBuilder() |
There was a problem hiding this comment.
There is a problem here. The encryptionKey passed into STSTokenIdentifier which encrypts the secretAccessKey, can be different than the currentKey in generateToken, which signs the whole token, if the key secret is rotated during the small time window.
There was a problem hiding this comment.
thanks so much for catching this. Updated - 10ef46a
| auditMap.put("x-amz-request-id", requestIdentifier.getRequestId()); | ||
| auditMap.put("x-amz-id-2", requestIdentifier.getAmzId()); | ||
| if (s3Auth != null) { | ||
| // For STS temporary credentials, record the originalAccessKeyId (the permanent principal that |
There was a problem hiding this comment.
Why do we need to log the originalAccessKeyId for every request which using sessionToken? Since the covert seems expensive to me in getStsOriginalAccessKeyId().
There was a problem hiding this comment.
yeah I was wondering why log originalAccessKeyId as well, since for every valid token, we have om audit log to tell us who the creator is, and previously we ensured that all om followers would have the same information for created tokens. updated to remove this audit entry - 99a1064
|
Thanks @fmorg-git , overall looks good. |
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
This PR updates STS token revocation to key the revocation table by originalAccessKeyId (with a replicated revocation cutoff time) instead of sessionToken, and augments auditing to include the original access key ID extracted from STS session tokens.
Changes:
- Switch revocation storage/cleanup and revoke RPC/CLI to use
originalAccessKeyId -> revocationTimeMillisand revoke only tokens created strictly before the cutoff. - Add
creationTimeto STS tokens and enforce it in token validation. - Enhance S3 Gateway audit logging to include
originalAccessKeyId (unverified)decoded from the presented session token.
Reviewed changes
Copilot reviewed 38 out of 39 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/util/TestAuditUtils.java | Adds unit tests for extracting originalAccessKeyId from STS session tokens for auditing. |
| hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestEndpointBase.java | Adds audit tests ensuring STS original access key ID is included/omitted appropriately. |
| hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/client/ClientProtocolStub.java | Updates revoke API stub signature to accept originalAccessKeyId. |
| hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/AuditUtils.java | Implements decoding of originalAccessKeyId from a session token for audit logging. |
| hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java | Records (unverified) originalAccessKeyId in the S3G audit map for STS requests. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenSecretManager.java | Extends tests to assert STS tokens include creationTime. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenIdentifier.java | Migrates tests to Params builder and verifies protobuf includes issue/creation time. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSTokenEncryption.java | Adds creation time assertions for encrypted STS token identifiers. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSSecurityUtil.java | Adds tests for runtime decode failures and creationTime being required. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java | Updates revocation tests to use cutoff time keyed by originalAccessKeyId. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/service/TestRevokedSTSTokenCleanupService.java | Updates cleanup tests for the new key/value semantics and request fields. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/request/s3/security/TestS3RevokeSTSTokenRequest.java | Refactors revoke request tests to validate access, unknown IDs, and replicated fields. |
| hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/om/TestOmMetadataManager.java | Updates metadata table tests to store/look up revocation entries by originalAccessKeyId. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenSecretManager.java | Sets token creationTime and passes it via new Params object when issuing STS tokens. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenIdentifier.java | Adds creationTime, serializes it to issueDate, and updates equality/hash/toString. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java | Treats runtime decode failures as invalid-token and requires creationTime. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java | Revokes based on per-originalAccessKeyId cutoff and token creation time. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/service/RevokedSTSTokenCleanupService.java | Cleans up revocation cutoffs keyed by originalAccessKeyId with max-token-lifetime retention. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/security/S3RevokeSTSTokenResponse.java | Stores revocation cutoff time for an originalAccessKeyId in the DB batch. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/response/s3/security/S3DeleteRevokedSTSTokensResponse.java | Deletes revocation entries by originalAccessKeyId. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3RevokeSTSTokenRequest.java | Validates input, captures replicated cutoff, updates cache, and audits originalAccessKeyId. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3DeleteRevokedSTSTokensRequest.java | Updates request handling to delete entries by originalAccessKeyId. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/s3/security/S3AssumeRoleRequest.java | Centralizes STS temp access key constants and standardizes audit param name. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/request/OMClientRequest.java | Uses shared STS token prefix constant rather than request-class constant. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/ratis/OzoneManagerStateMachine.java | Rehydrates ThreadLocal STS identifier using Params (setting creation/expiry to Instant.MAX). |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/codec/OMDBDefinition.java | Updates DB definition docs for new revocation key/value semantics. |
| hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/om/OmMetadataManagerImpl.java | Updates table comment to reflect originalAccessKeyId -> revocationTimeMillis. |
| hadoop-ozone/interface-client/src/main/proto/OmClientProtocol.proto | Changes revoke request field, adds replicated update request, and updates delete request fields. |
| hadoop-ozone/dist/src/main/smoketest/security/ozone-secure-sts.robot | Updates smoketests for new revoke CLI, post-revoke assume-role behavior, and malformed token. |
| hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocolPB/OzoneManagerProtocolClientSideTranslatorPB.java | Updates revoke RPC to send originalAccessKeyId. |
| hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/protocol/OzoneManagerProtocol.java | Updates revoke API contract to revoke by originalAccessKeyId. |
| hadoop-ozone/common/src/main/java/org/apache/hadoop/ozone/om/helpers/S3STSUtils.java | Adds shared STS token/access-key constants used across OM components. |
| hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java | Updates client method to revoke by originalAccessKeyId. |
| hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/protocol/ClientProtocol.java | Updates client protocol signature/docs for revoke-by-originalAccessKeyId. |
| hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/ObjectStore.java | Updates ObjectStore API/docs for revoke-by-originalAccessKeyId. |
| hadoop-ozone/cli-shell/src/main/java/org/apache/hadoop/ozone/shell/s3/RevokeSTSTokenHandler.java | Updates CLI option/UX and output to revoke by originalAccessKeyId. |
| hadoop-hdds/docs/content/design/ozone-sts.md | Updates design docs for creationTime and revocation cutoff semantics. |
| hadoop-hdds/common/src/main/resources/ozone-default.xml | Updates cleanup service description to match new retention semantics. |
| hadoop-hdds/common/src/main/java/org/apache/hadoop/ozone/OzoneConsts.java | Adds constants for STS audit param names. |
Suppressed comments (1)
hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/AuditUtils.java:1
- This decodes and logs a client-controlled value (marked as unverified), but it does not verify the protobuf
typeisS3_STS_TOKENand it does not bound/sanitize the extracted value size/content. A malformed token could inject an arbitraryoriginalAccessKeyIdinto audit logs or bloat audit records. Consider (1) checkingOMTokenProto.getType()before readingoriginalAccessKeyId, and (2) enforcing a strict max length / allowed character set (or returning null) before adding it to audit params.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
… of having UpdateRevokeSTSTokenRequest
…RevokeSTSTokenRequest
| .setSessionPolicy(sessionPolicy) | ||
| .setEncryptionKey(encryptionKey) | ||
| .build()); | ||
| identifier.setSecretKeyId(secretKey.getId()); |
There was a problem hiding this comment.
How about set the entire ManagedSecretKey to the STSTokenIdentifier builder? So we can avoid check id, and refetch the ManageedSecretKey again?
There was a problem hiding this comment.
the ManagedSecretKey is fetched only once and passed directly into generateToken() method used to sign. It doesn't appear to be a good idea to encode the entire ManagedSecretKey into the STS token itself (possible security risk).
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 34 out of 35 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSTokenIdentifier.java:266
fromProtoBuf()only setscreationTimewhenissueDateis present. Combined withSTSSecurityUtil.ensureEssentialFieldsArePresentInToken()requiring a non-nullcreationTime, this will cause previously-issued STS tokens (which may not haveissueDate) to be rejected after upgrade. Consider defaultingcreationTimewhenissueDateis absent (eg derive it frommaxDateand the max STS duration) to preserve compatibility during rolling upgrades.
if (token.hasIssueDate()) {
this.creationTime = Instant.ofEpochMilli(token.getIssueDate());
}
if (token.hasOriginalAccessKeyId()) {
… on replicated request Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Please describe your PR in detail:
creationTimefield. the revocation logic is updated such that only the tokens that were created by theoriginalAccessKeyIdbefore the entry in rocksDB is added are revoked - all tokens created by theoriginalAccessKeyIdafter that cutoff point will not be revoked.STSSecurityUtil).What is the link to the Apache JIRA
https://issues.apache.org/jira/browse/HDDS-16110
How was this patch tested?
(unverified):