This is an automated email from the ASF dual-hosted git repository.
sammichen pushed a commit to branch HDDS-13323-sts
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/HDDS-13323-sts by this push:
new f9bec4bde8d HDDS-14711. [STS] Ensure accessKeyId is valid for
sessionToken (#9820)
f9bec4bde8d is described below
commit f9bec4bde8ddc51126e1fd52cf9b64c518b2d65e
Author: fmorg-git <[email protected]>
AuthorDate: Wed Feb 25 18:15:11 2026 -0800
HDDS-14711. [STS] Ensure accessKeyId is valid for sessionToken (#9820)
---
.../hadoop/ozone/security/S3SecurityUtil.java | 8 +++
.../hadoop/ozone/security/TestS3SecurityUtil.java | 62 ++++++++++++++++++----
2 files changed, 61 insertions(+), 9 deletions(-)
diff --git
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
index 17b74bb7417..923bf9d9a78 100644
---
a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
+++
b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/S3SecurityUtil.java
@@ -86,6 +86,14 @@ public static void validateS3Credential(OMRequest omRequest,
throw new OMException("STS token no longer valid:
OriginalAccessKeyId principal revoked", REVOKED_TOKEN);
}
+ // Ensure the access key ID in the request matches the one encoded
in the token.
+ // This prevents using a valid token and secretKey to authenticate
arbitrary accessKeyIds.
+ final String requestAccessId =
omRequest.getS3Authentication().getAccessId();
+ if
(!requestAccessId.equals(stsTokenIdentifier.getTempAccessKeyId())) {
+ throw new OMException(
+ "STS token validation failed - accessKeyId is invalid for
session token", INVALID_TOKEN);
+ }
+
// HMAC signature and expiration were validated above. Now validate
AWS signature.
validateSTSTokenAwsSignature(stsTokenIdentifier, omRequest);
OzoneManager.setStsTokenIdentifier(stsTokenIdentifier);
diff --git
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
index d642c6e5ace..d82b338ce8f 100644
---
a/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
+++
b/hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestS3SecurityUtil.java
@@ -18,6 +18,7 @@
package org.apache.hadoop.ozone.security;
import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INTERNAL_ERROR;
+import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_TOKEN;
import static
org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.REVOKED_TOKEN;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -57,6 +58,7 @@
public class TestS3SecurityUtil {
private static final byte[] ENCRYPTION_KEY = new byte[5];
private static final TestClock CLOCK = TestClock.newInstance();
+ private static final String TEMP_ACCESS_KEY_ID = "temp-access-key-id";
{
ThreadLocalRandom.current().nextBytes(ENCRYPTION_KEY);
@@ -132,6 +134,33 @@ public void
testValidateS3CredentialFailsWhenOriginalAccessKeyIdCheckThrows() th
.setExpectedMessage("Could not determine if original principal is
revoked"));
}
+ @Test
+ public void
testValidateS3CredentialFailsWhenRequestAccessIdDoesNotMatchTokenOwner() throws
Exception {
+ validateS3CredentialHelper(
+ new TestConfig()
+ .setRequestAccessId("some-other-access-id")
+ .setExpectedResult(INVALID_TOKEN)
+ .setExpectedMessage("STS token validation failed - accessKeyId is
invalid for session token"));
+ }
+
+ @Test
+ public void testValidateS3CredentialFailsWhenRequestAccessIdMissing() throws
Exception {
+ validateS3CredentialHelper(
+ new TestConfig()
+ .setIncludeAccessId(false)
+ .setExpectedResult(INVALID_TOKEN)
+ .setExpectedMessage("STS token validation failed - accessKeyId is
invalid for session token"));
+ }
+
+ @Test
+ public void testValidateS3CredentialFailsWhenRequestAccessIdEmpty() throws
Exception {
+ validateS3CredentialHelper(
+ new TestConfig()
+ .setRequestAccessId("")
+ .setExpectedResult(INVALID_TOKEN)
+ .setExpectedMessage("STS token validation failed - accessKeyId is
invalid for session token"));
+ }
+
private void validateS3CredentialHelper(TestConfig config) throws Exception {
try (OzoneManager ozoneManager = mock(OzoneManager.class)) {
when(ozoneManager.isSecurityEnabled()).thenReturn(true);
@@ -178,7 +207,8 @@ private void validateS3CredentialHelper(TestConfig config)
throws Exception {
awsV4AuthValidatorMock.when(() ->
AWSV4AuthValidator.validateRequest(anyString(), anyString(), anyString()))
.thenReturn(true);
- final OMRequest omRequest =
createRequestWithSessionToken(sessionToken);
+ final OMRequest omRequest = createRequestWithSessionToken(
+ config.requestAccessId, config.includeAccessId);
if (config.expectedResult != null) {
final OMException omException = assertThrows(
@@ -199,19 +229,20 @@ private void validateS3CredentialHelper(TestConfig
config) throws Exception {
private STSTokenIdentifier createSTSTokenIdentifier() {
return new STSTokenIdentifier(
- "temp-access-key-id", "original-access-key-id",
"arn:aws:iam::123456789012:role/test-role",
+ TEMP_ACCESS_KEY_ID, "original-access-key-id",
"arn:aws:iam::123456789012:role/test-role",
CLOCK.instant().plusSeconds(3600), "secret-access-key",
"session-policy",
ENCRYPTION_KEY);
}
- @SuppressWarnings("SameParameterValue")
- private static OMRequest createRequestWithSessionToken(String sessionToken) {
- final S3Authentication s3Authentication = S3Authentication.newBuilder()
- .setAccessId("accessKeyId")
+ private static OMRequest createRequestWithSessionToken(String accessId,
boolean includeAccessId) {
+ final S3Authentication.Builder s3AuthenticationBuilder =
S3Authentication.newBuilder()
.setStringToSign("string-to-sign")
.setSignature("signature")
- .setSessionToken(sessionToken)
- .build();
+ .setSessionToken("session-token");
+ if (includeAccessId) {
+ s3AuthenticationBuilder.setAccessId(accessId);
+ }
+ final S3Authentication s3Authentication = s3AuthenticationBuilder.build();
return OMRequest.newBuilder()
.setClientId(UUID.randomUUID().toString())
@@ -223,12 +254,14 @@ private static OMRequest
createRequestWithSessionToken(String sessionToken) {
/**
* Helper class to create various scenarios for testing.
*/
- private static class TestConfig {
+ private static final class TestConfig {
private OMMetadataManager metadataManager = mock(OMMetadataManager.class);
private Table<String, Long> revokedSTSTokenTable = new
InMemoryTestTable<>();
private boolean isTokenRevoked = false;
private boolean isOriginalAccessKeyIdRevoked = false;
private boolean shouldOriginalAccessKeyIdCheckThrowError = false;
+ private String requestAccessId = TEMP_ACCESS_KEY_ID;
+ private boolean includeAccessId = true;
private OMException.ResultCodes expectedResult = null;
private String expectedMessage = null;
@@ -261,6 +294,17 @@ TestConfig
setShouldOriginalAccessKeyIdCheckThrowError(boolean isError) {
return this;
}
+ TestConfig setRequestAccessId(String requestAccessId) {
+ this.requestAccessId = requestAccessId;
+ return this;
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ TestConfig setIncludeAccessId(boolean includeAccessId) {
+ this.includeAccessId = includeAccessId;
+ return this;
+ }
+
TestConfig setExpectedResult(OMException.ResultCodes result) {
this.expectedResult = result;
return this;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]