diff --git a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java index 44d8b63b973f..c414708cebee 100644 --- a/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java +++ b/hadoop-ozone/ozone-manager/src/main/java/org/apache/hadoop/ozone/security/STSSecurityUtil.java @@ -18,6 +18,7 @@ package org.apache.hadoop.ozone.security; import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.INVALID_TOKEN; +import static org.apache.hadoop.ozone.om.exceptions.OMException.ResultCodes.TOKEN_EXPIRED; import com.google.common.annotations.VisibleForTesting; import com.google.protobuf.InvalidProtocolBufferException; @@ -71,7 +72,7 @@ public static STSTokenIdentifier constructValidateAndDecryptSTSToken(String sess * @throws SecretManager.InvalidToken if the token is invalid */ private static STSTokenIdentifier verifyAndDecryptToken(Token token, - SecretKeyClient secretKeyClient, Clock clock) throws SecretManager.InvalidToken { + SecretKeyClient secretKeyClient, Clock clock) throws SecretManager.InvalidToken, OMException { if (!STSTokenIdentifier.KIND_NAME.equals(token.getKind())) { throw new SecretManager.InvalidToken("Invalid STS token - kind is incorrect: " + token.getKind()); } @@ -100,6 +101,8 @@ private static STSTokenIdentifier verifyAndDecryptToken(Token STSSecurityUtil.constructValidateAndDecryptSTSToken(tokenString, secretKeyClient, clock)) .isInstanceOf(OMException.class) - .hasMessageContaining("Invalid STS token format: Invalid STS token - token expired at"); + .satisfies(exception -> assertThat(((OMException) exception).getResult()).isEqualTo(TOKEN_EXPIRED)) + .hasMessageContaining("Invalid STS token - token expired at"); } @Test @@ -236,6 +238,8 @@ public void testConstructValidateAndDecryptSTSTokenExpiredSecretKey() throws Exc // Create a mock secret key that is expired final ManagedSecretKey expiredSecretKey = mock(ManagedSecretKey.class); when(expiredSecretKey.isExpired()).thenReturn(true); + final Instant now = Instant.now(); + when(expiredSecretKey.getExpiryTime()).thenReturn(now); final SecretKeyClient mockKeyClient = mock(SecretKeyClient.class); when(mockKeyClient.getSecretKey(any())).thenReturn(expiredSecretKey); @@ -244,9 +248,8 @@ public void testConstructValidateAndDecryptSTSTokenExpiredSecretKey() throws Exc assertThatThrownBy(() -> STSSecurityUtil.constructValidateAndDecryptSTSToken(validTokenString, mockKeyClient, clock)) .isInstanceOf(OMException.class) - .hasMessage( - "Invalid STS token format: Invalid STS token - could not readFromByteArray: Token cannot be " + - "verified due to expired secret key " + secretKeyId); + .satisfies(exception -> assertThat(((OMException) exception).getResult()).isEqualTo(TOKEN_EXPIRED)) + .hasMessage("Token cannot be verified due to expired secret key: " + secretKeyId + " Token expired at " + now); } @Test diff --git a/hadoop-ozone/s3gateway/pom.xml b/hadoop-ozone/s3gateway/pom.xml index bebe6d1d07ba..7278c9e5315f 100644 --- a/hadoop-ozone/s3gateway/pom.xml +++ b/hadoop-ozone/s3gateway/pom.xml @@ -30,6 +30,10 @@ + + com.fasterxml.jackson.core + jackson-annotations + com.fasterxml.jackson.core jackson-databind diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java index 1ac30f49797c..21d00cd419bb 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketAclHandler.java @@ -113,6 +113,8 @@ Response handleGetRequest(S3RequestContext context, String bucketName) auditReadFailure(context.getAction(), ex); if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex); } else { @@ -232,6 +234,8 @@ Response handlePutRequest(S3RequestContext context, String bucketName, InputStre auditWriteFailure(context.getAction(), exception); if (exception.getResult() == ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, exception); + } else if (isExpiredToken(exception)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, exception); } else if (isAccessDenied(exception)) { throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, exception); } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketCrudHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketCrudHandler.java index 982838d0dd04..81c9b2836a30 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketCrudHandler.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketCrudHandler.java @@ -111,6 +111,8 @@ Response handleDeleteRequest(S3RequestContext context, String bucketName) throw newError(S3ErrorTable.BUCKET_NOT_EMPTY, bucketName, ex); } else if (ex.getResult() == OMException.ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex); } else { diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java index 18ba9f34934f..87e502915454 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/BucketEndpoint.java @@ -163,14 +163,7 @@ public Response get( } catch (OMException ex) { auditReadFailure(s3GAction, ex); getMetrics().updateGetBucketFailureStats(startNanos); - if (isAccessDenied(ex)) { - throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex); - } else if (ex.getResult() == ResultCodes.FILE_NOT_FOUND) { - // File not found, continue and send normal response with 0 keyCount - LOG.debug("Key Not found prefix: {}", prefix); - } else { - throw ex; - } + handleOMException(ex, bucketName, prefix); } catch (Exception ex) { getMetrics().updateGetBucketFailureStats(startNanos); auditReadFailure(s3GAction, ex); @@ -210,53 +203,64 @@ public Response get( String lastKey = null; int count = 0; if (maxKeys > 0) { - while (ozoneKeyIterator != null && ozoneKeyIterator.hasNext()) { - OzoneKey next = ozoneKeyIterator.next(); - if (bucket != null && bucket.getBucketLayout().isFileSystemOptimized() && - StringUtils.isNotEmpty(prefix) && - !next.getName().startsWith(prefix)) { - // prefix has delimiter but key don't have - // example prefix: dir1/ key: dir123 - continue; - } - if (startAfter != null && count == 0 && Objects.equals(startAfter, next.getName())) { - continue; - } - String relativeKeyName = next.getName().substring(prefix.length()); - - int depth = StringUtils.countMatches(relativeKeyName, delimiter); - if (!StringUtils.isEmpty(delimiter)) { - if (depth > 0) { - // means key has multiple delimiters in its value. - // ex: dir/dir1/dir2, where delimiter is "/" and prefix is dir/ - String dirName = relativeKeyName.substring(0, relativeKeyName - .indexOf(delimiter)); - if (!dirName.equals(prevDir)) { - response.addPrefix(EncodingTypeObject.createNullable( - prefix + dirName + delimiter, encodingType)); - prevDir = dirName; + try { + while (ozoneKeyIterator != null && ozoneKeyIterator.hasNext()) { + OzoneKey next = ozoneKeyIterator.next(); + if (bucket != null && bucket.getBucketLayout().isFileSystemOptimized() && + StringUtils.isNotEmpty(prefix) && + !next.getName().startsWith(prefix)) { + // prefix has delimiter but key don't have + // example prefix: dir1/ key: dir123 + continue; + } + if (startAfter != null && count == 0 && Objects.equals(startAfter, next.getName())) { + continue; + } + String relativeKeyName = next.getName().substring(prefix.length()); + + int depth = StringUtils.countMatches(relativeKeyName, delimiter); + if (!StringUtils.isEmpty(delimiter)) { + if (depth > 0) { + // means key has multiple delimiters in its value. + // ex: dir/dir1/dir2, where delimiter is "/" and prefix is dir/ + String dirName = relativeKeyName.substring(0, relativeKeyName + .indexOf(delimiter)); + if (!dirName.equals(prevDir)) { + response.addPrefix(EncodingTypeObject.createNullable( + prefix + dirName + delimiter, encodingType)); + prevDir = dirName; + count++; + } + } else if (relativeKeyName.endsWith(delimiter)) { + // means or key is same as prefix with delimiter at end and ends with + // delimiter. ex: dir/, where prefix is dir and delimiter is / + response.addPrefix( + EncodingTypeObject.createNullable(relativeKeyName, encodingType)); + count++; + } else { + // means our key is matched with prefix if prefix is given and it + // does not have any common prefix. + addKey(response, next); count++; } - } else if (relativeKeyName.endsWith(delimiter)) { - // means or key is same as prefix with delimiter at end and ends with - // delimiter. ex: dir/, where prefix is dir and delimiter is / - response.addPrefix( - EncodingTypeObject.createNullable(relativeKeyName, encodingType)); - count++; } else { - // means our key is matched with prefix if prefix is given and it - // does not have any common prefix. addKey(response, next); count++; } - } else { - addKey(response, next); - count++; - } - if (count == maxKeys) { - lastKey = next.getName(); - break; + if (count == maxKeys) { + lastKey = next.getName(); + break; + } + } + } catch (RuntimeException ex) { + getMetrics().updateGetBucketFailureStats(startNanos); + auditReadFailure(s3GAction, ex); + if (ex.getCause() instanceof OMException) { + final OMException omException = (OMException) ex.getCause(); + handleOMException(omException, bucketName, prefix); + } else { + throw ex; } } } @@ -362,7 +366,9 @@ public Response listMultipartUploads( } catch (OMException exception) { auditReadFailure(s3GAction, exception); getMetrics().updateListMultipartUploadsFailureStats(startNanos); - if (isAccessDenied(exception)) { + if (isExpiredToken(exception)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, prefix, exception); + } else if (isAccessDenied(exception)) { throw newError(S3ErrorTable.ACCESS_DENIED, prefix, exception); } throw exception; @@ -517,4 +523,17 @@ private void addHandler(BucketOperationHandler handler) { copyDependenciesTo(handler); handlers.add(handler); } + + private void handleOMException(OMException ex, String bucketName, String prefix) throws OMException { + if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName, ex); + } else if (isAccessDenied(ex)) { + throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex); + } else if (ex.getResult() == ResultCodes.FILE_NOT_FOUND) { + // File not found, continue and send normal response with 0 keyCount + LOG.debug("Key Not found prefix: {}", prefix); + } else { + throw ex; + } + } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java index 3d7f70d06c35..53bba420e67e 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/EndpointBase.java @@ -194,6 +194,8 @@ protected OzoneBucket getBucket(OzoneVolume volume, String bucketName) } catch (OMException ex) { if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex); } else if (ex.getResult() == ResultCodes.INVALID_TOKEN) { throw newError(S3ErrorTable.ACCESS_DENIED, s3Auth.getAccessID(), ex); @@ -259,6 +261,8 @@ protected OzoneBucket getBucket(String bucketName) if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND || ex.getResult() == ResultCodes.VOLUME_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucketName, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex); } else if (ex.getResult() == ResultCodes.INVALID_TOKEN) { throw newError(S3ErrorTable.ACCESS_DENIED, s3Auth.getAccessID(), ex); @@ -294,6 +298,8 @@ protected String createS3Bucket(String bucketName) throws getMetrics().updateCreateBucketFailureStats(startNanos); if (ex.getResult() == ResultCodes.PERMISSION_DENIED) { throw newError(S3ErrorTable.ACCESS_DENIED, bucketName, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex); } else if (ex.getResult() == ResultCodes.INVALID_TOKEN) { throw newError(S3ErrorTable.ACCESS_DENIED, s3Auth.getAccessID(), ex); @@ -322,6 +328,8 @@ protected void deleteS3Bucket(String s3BucketName) if (ex.getResult() == ResultCodes.PERMISSION_DENIED) { throw newError(S3ErrorTable.ACCESS_DENIED, s3BucketName, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), ex); } else if (ex.getResult() == ResultCodes.INVALID_TOKEN) { throw newError(S3ErrorTable.ACCESS_DENIED, s3Auth.getAccessID(), ex); @@ -377,6 +385,8 @@ private Iterator iterateBuckets( } else if (e.getResult() == ResultCodes.PERMISSION_DENIED) { throw newError(S3ErrorTable.ACCESS_DENIED, "listBuckets", e); + } else if (isExpiredToken(e)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), e); } else if (e.getResult() == ResultCodes.INVALID_TOKEN) { throw newError(S3ErrorTable.ACCESS_DENIED, s3Auth.getAccessID(), e); @@ -685,6 +695,10 @@ protected boolean isAccessDenied(OMException ex) { || result == ResultCodes.REVOKED_TOKEN; } + protected boolean isExpiredToken(OMException ex) { + return ex.getResult() == ResultCodes.TOKEN_EXPIRED; + } + protected ReplicationConfig getReplicationConfig(OzoneBucket ozoneBucket) throws OS3Exception { String storageType = getHeaders().getHeaderString(STORAGE_CLASS_HEADER); String storageConfig = getHeaders().getHeaderString(CUSTOM_METADATA_HEADER_PREFIX + STORAGE_CONFIG_HEADER); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java index 69edae429207..5d31728929fd 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/MultipartKeyHandler.java @@ -164,6 +164,8 @@ private Response listParts(OzoneBucket ozoneBucket, String key, String uploadId, } catch (OMException ex) { if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { throw newError(NO_SUCH_UPLOAD, uploadId, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName + "/" + key + "/" + uploadId, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, bucketName + "/" + key + "/" + uploadId, ex); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java index b18cf35d0d32..d97c514f9ae6 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpoint.java @@ -179,6 +179,8 @@ public Response put( " considered as Unix Paths. Path has Violated FS Semantics " + "which caused put operation to fail."); throw os3Exception; + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, keyPath, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); } else if (ex.getResult() == ResultCodes.QUOTA_EXCEEDED) { @@ -375,6 +377,8 @@ public Response get( } catch (OMException ex) { if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_KEY, keyPath, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, keyPath, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { @@ -554,6 +558,8 @@ public Response head( if (ex.getResult() == ResultCodes.KEY_NOT_FOUND) { // Just return 404 with no content return Response.status(Status.NOT_FOUND).build(); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, keyPath, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { @@ -640,6 +646,8 @@ public Response delete( // NOT_FOUND is not a problem, AWS doesn't throw exception for missing // keys. Just return 204 return Response.status(Status.NO_CONTENT).build(); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, keyPath, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, keyPath, ex); } else if (ex.getResult() == ResultCodes.NOT_SUPPORTED_OPERATION) { @@ -714,6 +722,9 @@ public Response initializeMultipartUpload( } catch (OMException ex) { auditWriteFailure(s3GAction, ex); getMetrics().updateInitMultipartUploadFailureStats(startNanos); + if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, key, ex); + } if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, key, ex); } @@ -794,6 +805,10 @@ public Response completeMultipartUpload( "considered as Unix Paths. A directory already exists with a " + "given KeyName caused failure for MPU"); throw os3Exception; + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, key, ex); + } else if (isAccessDenied(ex)) { + throw newError(S3ErrorTable.ACCESS_DENIED, key, ex); } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, bucket, ex); } @@ -959,6 +974,8 @@ private Response createMultipartKey(OzoneVolume volume, OzoneBucket ozoneBucket, } if (ex.getResult() == ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { throw newError(NO_SUCH_UPLOAD, uploadID, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, bucketName + "/" + key, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, bucketName + "/" + key, ex); } else if (ex.getResult() == ResultCodes.INVALID_PART) { @@ -1114,6 +1131,8 @@ private CopyObjectResponse copyObject(OzoneVolume volume, throw newError(S3ErrorTable.NO_SUCH_KEY, sourceKey, ex); } else if (ex.getResult() == ResultCodes.BUCKET_NOT_FOUND) { throw newError(S3ErrorTable.NO_SUCH_BUCKET, sourceBucket, ex); + } else if (isExpiredToken(ex)) { + throw newError(S3ErrorTable.EXPIRED_TOKEN, destBucket + "/" + destkey, ex); } else if (isAccessDenied(ex)) { throw newError(S3ErrorTable.ACCESS_DENIED, destBucket + "/" + destkey, ex); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java index 767c11506dc1..7012734b1611 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/ObjectEndpointStreaming.java @@ -92,6 +92,8 @@ public static Pair put( " considered as Unix Paths. Path has Violated FS Semantics " + "which caused put operation to fail."); throw os3Exception; + } else if ((((OMException) ex).getResult() == OMException.ResultCodes.TOKEN_EXPIRED)) { + throw S3ErrorTable.newError(S3ErrorTable.EXPIRED_TOKEN, keyPath); } else if ((((OMException) ex).getResult() == OMException.ResultCodes.PERMISSION_DENIED)) { throw S3ErrorTable.newError(S3ErrorTable.ACCESS_DENIED, keyPath); @@ -230,6 +232,8 @@ public static Response createMultipartKey(OzoneBucket ozoneBucket, String key, OMException.ResultCodes.NO_SUCH_MULTIPART_UPLOAD_ERROR) { throw S3ErrorTable.newError(NO_SUCH_UPLOAD, uploadID); + } else if (ex.getResult() == OMException.ResultCodes.TOKEN_EXPIRED) { + throw S3ErrorTable.newError(S3ErrorTable.EXPIRED_TOKEN, ozoneBucket.getName() + "/" + key); } else if (ex.getResult() == OMException.ResultCodes.PERMISSION_DENIED) { throw S3ErrorTable.newError(S3ErrorTable.ACCESS_DENIED, ozoneBucket.getName() + "/" + key); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java index f93f4a7a4d7a..009e22c42cbf 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3Exception.java @@ -17,10 +17,12 @@ package org.apache.hadoop.ozone.s3.exception; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; +import com.google.common.base.Strings; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -59,6 +61,14 @@ public class OS3Exception extends RuntimeException { @XmlElement(name = "RequestId") private String requestId; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @XmlElement(name = "HostId") + private String hostId; + + @JsonInclude(JsonInclude.Include.NON_EMPTY) + @XmlElement(name = "Token-0") + private String token0; + @XmlTransient private int httpCode; @@ -125,6 +135,22 @@ public void setResource(String resource) { this.resource = resource; } + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public String getToken0() { + return token0; + } + + public void setToken0(String token0) { + this.token0 = token0; + } + public int getHttpCode() { return httpCode; } @@ -146,16 +172,20 @@ public String toXml() { //When we get exception log it, and return exception as xml from actual // exception data. So, falling back to construct from exception. - String formatString = "" + - "" + - "%s" + - "%s" + - "%s" + - "%s" + - ""; - return String.format(formatString, this.getCode(), - this.getErrorMessage(), this.getResource(), - this.getRequestId()); + final StringBuilder builder = new StringBuilder("") + .append("") + .append("").append(this.getCode()).append("") + .append("").append(this.getErrorMessage()).append("") + .append("").append(this.getResource()).append("") + .append("").append(this.getRequestId()).append(""); + if (!Strings.isNullOrEmpty(this.getHostId())) { + builder.append("").append(this.getHostId()).append(""); + } + if (!Strings.isNullOrEmpty(this.getToken0())) { + builder.append("").append(this.getToken0()).append(""); + } + builder.append(""); + return builder.toString(); } /** Create a copy with specific message. */ diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java index 5f110144c118..b576cb69b4c9 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/OS3ExceptionMapper.java @@ -18,10 +18,12 @@ package org.apache.hadoop.ozone.s3.exception; import javax.inject.Inject; +import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.ext.ExceptionMapper; import javax.ws.rs.ext.Provider; import org.apache.hadoop.ozone.s3.RequestIdentifier; +import org.apache.hadoop.ozone.s3.signature.SignatureInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,19 +34,33 @@ @Provider public class OS3ExceptionMapper implements ExceptionMapper { + private static final String EXPIRED_TOKEN = "ExpiredToken"; + private static final Logger LOG = LoggerFactory.getLogger(OS3ExceptionMapper.class); @Inject private RequestIdentifier requestIdentifier; + @Inject + private SignatureInfo signatureInfo; + @Override public Response toResponse(OS3Exception exception) { if (LOG.isDebugEnabled()) { LOG.debug("Returning exception. ex: {}", exception.toString()); } exception.setRequestId(requestIdentifier.getRequestId()); + exception.setHostId(requestIdentifier.getAmzId()); + if (EXPIRED_TOKEN.equals(exception.getCode()) && signatureInfo != null) { + final String sessionToken = signatureInfo.getSessionToken(); + if (sessionToken != null && !sessionToken.isEmpty()) { + exception.setToken0(sessionToken); + } + } return Response.status(exception.getHttpCode()) - .entity(exception.toXml()).build(); + .entity(exception.toXml()) + .type(MediaType.APPLICATION_XML) + .build(); } } diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java index 301f5940af67..c7baaa95080b 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java @@ -114,6 +114,9 @@ public final class S3ErrorTable { "AccessDenied", "User doesn't have the right to access this " + "resource.", HTTP_FORBIDDEN); + public static final OS3Exception EXPIRED_TOKEN = new OS3Exception( + "ExpiredToken", "The provided token has expired.", HTTP_FORBIDDEN); + public static final OS3Exception PRECOND_FAILED = new OS3Exception( "PreconditionFailed", "At least one of the pre-conditions you " + "specified did not hold", HTTP_PRECON_FAILED); diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestEndpointBase.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestEndpointBase.java index 2b29bb9bcfb0..89d5a26f21a5 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestEndpointBase.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestEndpointBase.java @@ -126,4 +126,15 @@ public void init() { } assertFalse(endpointBase.isAccessDenied(new OMException(ResultCodes.BUCKET_NOT_FOUND))); } + @Test + public void testExpiredTokenResultCode() { + final EndpointBase endpointBase = new EndpointBase() { + @Override + public void init() { } + }; + + assertTrue(endpointBase.isExpiredToken(new OMException(ResultCodes.TOKEN_EXPIRED))); + assertFalse(endpointBase.isExpiredToken(new OMException(ResultCodes.INVALID_TOKEN))); + } + } diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exceptions.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exceptions.java index a4ae1fce25b1..9c1eeb41aa38 100644 --- a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exceptions.java +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exceptions.java @@ -47,4 +47,27 @@ public void testOS3Exceptions() { ex.getRequestId()); assertEquals(expected, val); } + + @Test + public void testOS3ExceptionWithToken0() { + OS3Exception ex = new OS3Exception("ExpiredToken", "The provided token has expired.", 403); + ex = S3ErrorTable.newError(ex, "resource"); + ex.setRequestId(OzoneUtils.getRequestID()); + ex.setHostId(OzoneUtils.getRequestID()); + ex.setToken0("token-value"); + + final String val = ex.toXml(); + final String formatString = "%n" + + "%n" + + " %s%n" + + " %s%n" + + " %s%n" + + " %s%n" + + " %s%n" + + " %s%n" + + "%n"; + final String expected = String.format(formatString, ex.getCode(), ex.getErrorMessage(), ex.getResource(), + ex.getRequestId(), ex.getHostId(), ex.getToken0()); + assertEquals(expected, val); + } }