Skip to content

Commit 9b3577e

Browse files
authored
HDDS-14847. [STS] Expose ExpiredToken Error (#9935)
1 parent 1f723f7 commit 9b3577e

15 files changed

Lines changed: 230 additions & 68 deletions

File tree

hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package org.apache.hadoop.ozone.security;
1919

2020
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_TOKEN;
21+
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_EXPIRED;
2122

2223
import com.google.common.annotations.VisibleForTesting;
2324
import com.google.protobuf.InvalidProtocolBufferException;
@@ -71,7 +72,7 @@ public static STSTokenIdentifier constructValidateAndDecryptSTSToken(String sess
7172
* @throws SecretManager.InvalidToken if the token is invalid
7273
*/
7374
private static STSTokenIdentifier verifyAndDecryptToken(Token<STSTokenIdentifier> token,
74-
SecretKeyClient secretKeyClient, Clock clock) throws SecretManager.InvalidToken {
75+
SecretKeyClient secretKeyClient, Clock clock) throws SecretManager.InvalidToken, OMException {
7576
if (!STSTokenIdentifier.KIND_NAME.equals(token.getKind())) {
7677
throw new SecretManager.InvalidToken("Invalid STS token - kind is incorrect: " + token.getKind());
7778
}
@@ -100,6 +101,8 @@ private static STSTokenIdentifier verifyAndDecryptToken(Token<STSTokenIdentifier
100101
secretKey = getValidatedSecretKey(secretKeyId, secretKeyClient);
101102
tokenId.setEncryptionKey(secretKey.getSecretKey().getEncoded());
102103
tokenId.readFromByteArray(tokenBytes);
104+
} catch (OMException e) {
105+
throw e;
103106
} catch (IOException e) {
104107
throw new SecretManager.InvalidToken("Invalid STS token - could not readFromByteArray: " + e.getMessage());
105108
}
@@ -109,7 +112,7 @@ private static STSTokenIdentifier verifyAndDecryptToken(Token<STSTokenIdentifier
109112

110113
// Check expiration
111114
if (tokenId.isExpired(clock.instant())) {
112-
throw new SecretManager.InvalidToken("Invalid STS token - token expired at " + tokenId.getExpiry());
115+
throw new OMException("Invalid STS token - token expired at " + tokenId.getExpiry(), TOKEN_EXPIRED);
113116
}
114117

115118
// Verify token signature against the original identifier bytes
@@ -121,7 +124,7 @@ private static STSTokenIdentifier verifyAndDecryptToken(Token<STSTokenIdentifier
121124
}
122125

123126
private static ManagedSecretKey getValidatedSecretKey(UUID secretKeyId, SecretKeyClient secretKeyClient)
124-
throws SecretManager.InvalidToken {
127+
throws SecretManager.InvalidToken, OMException {
125128
if (secretKeyId == null) {
126129
throw new SecretManager.InvalidToken("STS token missing secret key ID");
127130
}
@@ -138,7 +141,9 @@ private static ManagedSecretKey getValidatedSecretKey(UUID secretKeyId, SecretKe
138141
}
139142

140143
if (secretKey.isExpired()) {
141-
throw new SecretManager.InvalidToken("Token cannot be verified due to expired secret key " + secretKeyId);
144+
throw new OMException(
145+
"Token cannot be verified due to expired secret key: " + secretKeyId + " Token expired at " +
146+
secretKey.getExpiryTime(), TOKEN_EXPIRED);
142147
}
143148

144149
return secretKey;

hadoop-ozone/ozone-manager/src/test/java/org/apache/hadoop/ozone/security/TestSTSSecurityUtil.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.hadoop.ozone.security;
1919

20+
import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_EXPIRED;
2021
import static org.assertj.core.api.Assertions.assertThat;
2122
import static org.assertj.core.api.Assertions.assertThatThrownBy;
2223
import static org.mockito.ArgumentMatchers.any;
@@ -179,7 +180,8 @@ public void testConstructValidateAndDecryptSTSTokenExpired() throws Exception {
179180
assertThatThrownBy(() ->
180181
STSSecurityUtil.constructValidateAndDecryptSTSToken(tokenString, secretKeyClient, clock))
181182
.isInstanceOf(OMException.class)
182-
.hasMessageContaining("Invalid STS token format: Invalid STS token - token expired at");
183+
.satisfies(exception -> assertThat(((OMException) exception).getResult()).isEqualTo(TOKEN_EXPIRED))
184+
.hasMessageContaining("Invalid STS token - token expired at");
183185
}
184186

185187
@Test
@@ -236,6 +238,8 @@ public void testConstructValidateAndDecryptSTSTokenExpiredSecretKey() throws Exc
236238
// Create a mock secret key that is expired
237239
final ManagedSecretKey expiredSecretKey = mock(ManagedSecretKey.class);
238240
when(expiredSecretKey.isExpired()).thenReturn(true);
241+
final Instant now = Instant.now();
242+
when(expiredSecretKey.getExpiryTime()).thenReturn(now);
239243

240244
final SecretKeyClient mockKeyClient = mock(SecretKeyClient.class);
241245
when(mockKeyClient.getSecretKey(any())).thenReturn(expiredSecretKey);
@@ -244,9 +248,8 @@ public void testConstructValidateAndDecryptSTSTokenExpiredSecretKey() throws Exc
244248
assertThatThrownBy(() ->
245249
STSSecurityUtil.constructValidateAndDecryptSTSToken(validTokenString, mockKeyClient, clock))
246250
.isInstanceOf(OMException.class)
247-
.hasMessage(
248-
"Invalid STS token format: Invalid STS token - could not readFromByteArray: Token cannot be " +
249-
"verified due to expired secret key " + secretKeyId);
251+
.satisfies(exception -> assertThat(((OMException) exception).getResult()).isEqualTo(TOKEN_EXPIRED))
252+
.hasMessage("Token cannot be verified due to expired secret key: " + secretKeyId + " Token expired at " + now);
250253
}
251254

252255
@Test

hadoop-ozone/s3gateway/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
</properties>
3131

3232
<dependencies>
33+
<dependency>
34+
<groupId>com.fasterxml.jackson.core</groupId>
35+
<artifactId>jackson-annotations</artifactId>
36+
</dependency>
3337
<dependency>
3438
<groupId>com.fasterxml.jackson.core</groupId>
3539
<artifactId>jackson-databind</artifactId>

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ Response handleGetRequest(S3RequestContext context, String bucketName)
113113
auditReadFailure(context.getAction(), ex);
114114
if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) {
115115
throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex);
116+
} else if (isExpiredToken(ex)) {
117+
throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, ex);
116118
} else if (isAccessDenied(ex)) {
117119
throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex);
118120
} else {
@@ -232,6 +234,8 @@ Response handlePutRequest(S3RequestContext context, String bucketName, InputStre
232234
auditWriteFailure(context.getAction(), exception);
233235
if (exception.getResult() == ResultCodes.BUCKET_NOT_FOUND) {
234236
throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, exception);
237+
} else if (isExpiredToken(exception)) {
238+
throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, exception);
235239
} else if (isAccessDenied(exception)) {
236240
throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, exception);
237241
}

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketCrudHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ Response handleDeleteRequest(S3RequestContext context, String bucketName)
111111
throw newError(S3ErrorTable.BUCKET_NOT_EMPTY, bucketName, ex);
112112
} else if (ex.getResult() == OMException.ResultCodes.BUCKET_NOT_FOUND) {
113113
throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex);
114+
} else if (isExpiredToken(ex)) {
115+
throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, ex);
114116
} else if (isAccessDenied(ex)) {
115117
throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex);
116118
} else {

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java

Lines changed: 71 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -163,14 +163,7 @@ public Response get(
163163
} catch (OMException ex) {
164164
auditReadFailure(s3GAction, ex);
165165
getMetrics().updateGetBucketFailureStats(startNanos);
166-
if (isAccessDenied(ex)) {
167-
throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex);
168-
} else if (ex.getResult() == ResultCodes.FILE_NOT_FOUND) {
169-
// File not found, continue and send normal response with 0 keyCount
170-
LOG.debug("Key Not found prefix: {}", prefix);
171-
} else {
172-
throw ex;
173-
}
166+
handleOMException(ex, bucketName, prefix);
174167
} catch (Exception ex) {
175168
getMetrics().updateGetBucketFailureStats(startNanos);
176169
auditReadFailure(s3GAction, ex);
@@ -210,53 +203,67 @@ public Response get(
210203
String lastKey = null;
211204
int count = 0;
212205
if (maxKeys > 0) {
213-
while (ozoneKeyIterator != null && ozoneKeyIterator.hasNext()) {
214-
OzoneKey next = ozoneKeyIterator.next();
215-
if (bucket != null && bucket.getBucketLayout().isFileSystemOptimized() &&
216-
StringUtils.isNotEmpty(prefix) &&
217-
!next.getName().startsWith(prefix)) {
218-
// prefix has delimiter but key don't have
219-
// example prefix: dir1/ key: dir123
220-
continue;
221-
}
222-
if (startAfter != null && count == 0 && Objects.equals(startAfter, next.getName())) {
223-
continue;
224-
}
225-
String relativeKeyName = next.getName().substring(prefix.length());
226-
227-
int depth = StringUtils.countMatches(relativeKeyName, delimiter);
228-
if (!StringUtils.isEmpty(delimiter)) {
229-
if (depth > 0) {
230-
// means key has multiple delimiters in its value.
231-
// ex: dir/dir1/dir2, where delimiter is "/" and prefix is dir/
232-
String dirName = relativeKeyName.substring(0, relativeKeyName
233-
.indexOf(delimiter));
234-
if (!dirName.equals(prevDir)) {
235-
response.addPrefix(EncodingTypeObject.createNullable(
236-
prefix + dirName + delimiter, encodingType));
237-
prevDir = dirName;
206+
try {
207+
while (ozoneKeyIterator != null && ozoneKeyIterator.hasNext()) {
208+
OzoneKey next = ozoneKeyIterator.next();
209+
if (bucket != null && bucket.getBucketLayout().isFileSystemOptimized() &&
210+
StringUtils.isNotEmpty(prefix) &&
211+
!next.getName().startsWith(prefix)) {
212+
// prefix has delimiter but key don't have
213+
// example prefix: dir1/ key: dir123
214+
continue;
215+
}
216+
if (startAfter != null && count == 0 && Objects.equals(startAfter, next.getName())) {
217+
continue;
218+
}
219+
String relativeKeyName = next.getName().substring(prefix.length());
220+
221+
int depth = StringUtils.countMatches(relativeKeyName, delimiter);
222+
if (!StringUtils.isEmpty(delimiter)) {
223+
if (depth > 0) {
224+
// means key has multiple delimiters in its value.
225+
// ex: dir/dir1/dir2, where delimiter is "/" and prefix is dir/
226+
String dirName = relativeKeyName.substring(0, relativeKeyName
227+
.indexOf(delimiter));
228+
if (!dirName.equals(prevDir)) {
229+
response.addPrefix(EncodingTypeObject.createNullable(
230+
prefix + dirName + delimiter, encodingType));
231+
prevDir = dirName;
232+
count++;
233+
}
234+
} else if (relativeKeyName.endsWith(delimiter)) {
235+
// means or key is same as prefix with delimiter at end and ends with
236+
// delimiter. ex: dir/, where prefix is dir and delimiter is /
237+
response.addPrefix(
238+
EncodingTypeObject.createNullable(relativeKeyName, encodingType));
239+
count++;
240+
} else {
241+
// means our key is matched with prefix if prefix is given and it
242+
// does not have any common prefix.
243+
addKey(response, next);
238244
count++;
239245
}
240-
} else if (relativeKeyName.endsWith(delimiter)) {
241-
// means or key is same as prefix with delimiter at end and ends with
242-
// delimiter. ex: dir/, where prefix is dir and delimiter is /
243-
response.addPrefix(
244-
EncodingTypeObject.createNullable(relativeKeyName, encodingType));
245-
count++;
246246
} else {
247-
// means our key is matched with prefix if prefix is given and it
248-
// does not have any common prefix.
249247
addKey(response, next);
250248
count++;
251249
}
252-
} else {
253-
addKey(response, next);
254-
count++;
255-
}
256250

257-
if (count == maxKeys) {
258-
lastKey = next.getName();
259-
break;
251+
if (count == maxKeys) {
252+
lastKey = next.getName();
253+
break;
254+
}
255+
}
256+
} catch (RuntimeException ex) {
257+
getMetrics().updateGetBucketFailureStats(startNanos);
258+
auditReadFailure(s3GAction, ex);
259+
if (ex.getCause() instanceof OMException) {
260+
final OMException omException = (OMException) ex.getCause();
261+
if (omException.getResult() == ResultCodes.FILE_NOT_FOUND) {
262+
throw ex;
263+
}
264+
handleOMException(omException, bucketName, prefix);
265+
} else {
266+
throw ex;
260267
}
261268
}
262269
}
@@ -362,7 +369,9 @@ public Response listMultipartUploads(
362369
} catch (OMException exception) {
363370
auditReadFailure(s3GAction, exception);
364371
getMetrics().updateListMultipartUploadsFailureStats(startNanos);
365-
if (isAccessDenied(exception)) {
372+
if (isExpiredToken(exception)) {
373+
throw newError(S3ErrorTable.EXPIRED_TOKEN, prefix, exception);
374+
} else if (isAccessDenied(exception)) {
366375
throw newError(S3ErrorTable.ACCESS_DENIED, prefix, exception);
367376
}
368377
throw exception;
@@ -517,4 +526,17 @@ private void addHandler(BucketOperationHandler handler) {
517526
copyDependenciesTo(handler);
518527
handlers.add(handler);
519528
}
529+
530+
private void handleOMException(OMException ex, String bucketName, String prefix) throws OMException {
531+
if (isExpiredToken(ex)) {
532+
throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, ex);
533+
} else if (isAccessDenied(ex)) {
534+
throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex);
535+
} else if (ex.getResult() == ResultCodes.FILE_NOT_FOUND) {
536+
// File not found, continue and send normal response with 0 keyCount
537+
LOG.debug("Key Not found prefix: {}", prefix);
538+
} else {
539+
throw ex;
540+
}
541+
}
520542
}

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,8 @@ protected OzoneBucket getBucket(OzoneVolume volume, String bucketName)
194194
} catch (OMException ex) {
195195
if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) {
196196
throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex);
197+
} else if (isExpiredToken(ex)) {
198+
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex);
197199
} else if (ex.getResult() == ResultCodes.INVALID_TOKEN) {
198200
throw newError(S3ErrorTable.ACCESS_DENIED,
199201
s3Auth.getAccessID(), ex);
@@ -259,6 +261,8 @@ protected OzoneBucket getBucket(String bucketName)
259261
if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND
260262
|| ex.getResult() == ResultCodes.VOLUME_NOT_FOUND) {
261263
throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex);
264+
} else if (isExpiredToken(ex)) {
265+
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex);
262266
} else if (ex.getResult() == ResultCodes.INVALID_TOKEN) {
263267
throw newError(S3ErrorTable.ACCESS_DENIED,
264268
s3Auth.getAccessID(), ex);
@@ -294,6 +298,8 @@ protected String createS3Bucket(String bucketName) throws
294298
getMetrics().updateCreateBucketFailureStats(startNanos);
295299
if (ex.getResult() == ResultCodes.PERMISSION_DENIED) {
296300
throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex);
301+
} else if (isExpiredToken(ex)) {
302+
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex);
297303
} else if (ex.getResult() == ResultCodes.INVALID_TOKEN) {
298304
throw newError(S3ErrorTable.ACCESS_DENIED,
299305
s3Auth.getAccessID(), ex);
@@ -322,6 +328,8 @@ protected void deleteS3Bucket(String s3BucketName)
322328
if (ex.getResult() == ResultCodes.PERMISSION_DENIED) {
323329
throw newError(S3ErrorTable.ACCESS_DENIED,
324330
s3BucketName, ex);
331+
} else if (isExpiredToken(ex)) {
332+
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex);
325333
} else if (ex.getResult() == ResultCodes.INVALID_TOKEN) {
326334
throw newError(S3ErrorTable.ACCESS_DENIED,
327335
s3Auth.getAccessID(), ex);
@@ -377,6 +385,8 @@ private Iterator<? extends OzoneBucket> iterateBuckets(
377385
} else if (e.getResult() == ResultCodes.PERMISSION_DENIED) {
378386
throw newError(S3ErrorTable.ACCESS_DENIED,
379387
"listBuckets", e);
388+
} else if (isExpiredToken(e)) {
389+
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), e);
380390
} else if (e.getResult() == ResultCodes.INVALID_TOKEN) {
381391
throw newError(S3ErrorTable.ACCESS_DENIED,
382392
s3Auth.getAccessID(), e);
@@ -685,6 +695,10 @@ protected boolean isAccessDenied(OMException ex) {
685695
|| result == ResultCodes.REVOKED_TOKEN;
686696
}
687697

698+
protected boolean isExpiredToken(OMException ex) {
699+
return ex.getResult() == ResultCodes.TOKEN_EXPIRED;
700+
}
701+
688702
protected ReplicationConfig getReplicationConfig(OzoneBucket ozoneBucket) throws OS3Exception {
689703
String storageType = getHeaders().getHeaderString(STORAGE_CLASS_HEADER);
690704
String storageConfig = getHeaders().getHeaderString(CUSTOM_METADATA_HEADER_PREFIX + STORAGE_CONFIG_HEADER);

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ private Response listParts(OzoneBucket ozoneBucket, String key, String uploadId,
164164
} catch (OMException ex) {
165165
if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) {
166166
throw newError(NO_SUCH_UPLOAD, uploadId, ex);
167+
} else if (isExpiredToken(ex)) {
168+
throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName + "/" + key + "/" + uploadId, ex);
167169
} else if (isAccessDenied(ex)) {
168170
throw newError(S3ErrorTable.ACCESS_DENIED,
169171
bucketName + "/" + key + "/" + uploadId, ex);

0 commit comments

Comments
 (0)