Skip to content

Commit 1810c0d

Browse files
authored
HDDS-14861. [STS] Fix Latent S3 API issue when ListBuckets Missing a Required Permission (#9949)
1 parent 9504881 commit 1810c0d

4 files changed

Lines changed: 84 additions & 19 deletions

File tree

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

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -379,24 +379,33 @@ private Iterator<? extends OzoneBucket> iterateBuckets(
379379
OzoneVolume volume = getVolume();
380380
ownerSetter.accept(volume);
381381
return query.apply(volume);
382-
} catch (OMException e) {
383-
if (e.getResult() == ResultCodes.VOLUME_NOT_FOUND) {
384-
return Collections.emptyIterator();
385-
} else if (e.getResult() == ResultCodes.PERMISSION_DENIED) {
386-
throw newError(S3ErrorTable.ACCESS_DENIED,
387-
"listBuckets", e);
388-
} else if (isExpiredToken(e)) {
389-
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), e);
390-
} else if (e.getResult() == ResultCodes.INVALID_TOKEN) {
391-
throw newError(S3ErrorTable.ACCESS_DENIED,
392-
s3Auth.getAccessID(), e);
393-
} else if (e.getResult() == ResultCodes.TIMEOUT ||
394-
e.getResult() == ResultCodes.INTERNAL_ERROR) {
395-
throw newError(S3ErrorTable.INTERNAL_ERROR,
396-
"listBuckets", e);
397-
} else {
398-
throw e;
382+
} catch (RuntimeException e) {
383+
if (e.getCause() instanceof OMException) {
384+
return handleOMException((OMException) e.getCause());
399385
}
386+
throw e;
387+
} catch (OMException e) {
388+
return handleOMException(e);
389+
}
390+
}
391+
392+
private Iterator<OzoneBucket> handleOMException(OMException e) throws OMException {
393+
if (e.getResult() == ResultCodes.VOLUME_NOT_FOUND) {
394+
return Collections.emptyIterator();
395+
} else if (e.getResult() == ResultCodes.PERMISSION_DENIED) {
396+
throw newError(S3ErrorTable.ACCESS_DENIED,
397+
"listBuckets", e);
398+
} else if (isExpiredToken(e)) {
399+
throw newError(S3ErrorTable.EXPIRED_TOKEN, s3Auth.getAccessID(), e);
400+
} else if (e.getResult() == ResultCodes.INVALID_TOKEN) {
401+
throw newError(S3ErrorTable.ACCESS_DENIED,
402+
s3Auth.getAccessID(), e);
403+
} else if (e.getResult() == ResultCodes.TIMEOUT ||
404+
e.getResult() == ResultCodes.INTERNAL_ERROR) {
405+
throw newError(S3ErrorTable.INTERNAL_ERROR,
406+
"listBuckets", e);
407+
} else {
408+
throw e;
400409
}
401410
}
402411

hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/exception/S3ErrorTable.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ public final class S3ErrorTable {
115115
"resource.", HTTP_FORBIDDEN);
116116

117117
public static final OS3Exception EXPIRED_TOKEN = new OS3Exception(
118-
"ExpiredToken", "The provided token has expired.", HTTP_FORBIDDEN);
118+
"ExpiredToken", "The provided token has expired.", HTTP_BAD_REQUEST);
119119

120120
public static final OS3Exception PRECOND_FAILED = new OS3Exception(
121121
"PreconditionFailed", "At least one of the pre-conditions you " +

hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestEndpointBase.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@
2424
import static org.junit.jupiter.api.Assertions.assertFalse;
2525
import static org.junit.jupiter.api.Assertions.assertThrows;
2626
import static org.junit.jupiter.api.Assertions.assertTrue;
27+
import static org.mockito.ArgumentMatchers.anyString;
28+
import static org.mockito.Mockito.mock;
29+
import static org.mockito.Mockito.when;
2730

2831
import java.nio.charset.StandardCharsets;
2932
import java.util.Locale;
3033
import java.util.Map;
3134
import javax.ws.rs.core.MultivaluedHashMap;
3235
import javax.ws.rs.core.MultivaluedMap;
3336
import org.apache.hadoop.ozone.OzoneConsts;
37+
import org.apache.hadoop.ozone.client.OzoneVolume;
3438
import org.apache.hadoop.ozone.om.exceptions.OMException;
3539
import org.apache.hadoop.ozone.s3.exception.OS3Exception;
3640
import org.junit.jupiter.api.Test;
@@ -137,4 +141,45 @@ public void init() { }
137141
assertFalse(endpointBase.isExpiredToken(new OMException(ResultCodes.INVALID_TOKEN)));
138142
}
139143

144+
@Test
145+
public void testListS3BucketsHandlesRuntimeExceptionWrappingOMException() throws Exception {
146+
final EndpointBase endpointBase = new EndpointBase() {
147+
@Override
148+
public void init() { }
149+
150+
@Override
151+
protected OzoneVolume getVolume() {
152+
final OzoneVolume volume = mock(OzoneVolume.class);
153+
when(volume.listBuckets(anyString())).thenThrow(
154+
new RuntimeException(new OMException("Permission Denied", ResultCodes.PERMISSION_DENIED)));
155+
return volume;
156+
}
157+
};
158+
159+
final OS3Exception e = assertThrows(
160+
OS3Exception.class, () -> endpointBase.listS3Buckets(
161+
"prefix", volume -> { }), "listS3Buckets should fail.");
162+
163+
// Ensure we get the correct code
164+
assertEquals("AccessDenied", e.getCode());
165+
}
166+
167+
@Test
168+
public void testListS3BucketsHandlesRuntimeExceptionWrappingOMExceptionVolumeNotFound() throws Exception {
169+
final EndpointBase endpointBase = new EndpointBase() {
170+
@Override
171+
public void init() { }
172+
173+
@Override
174+
protected OzoneVolume getVolume() {
175+
final OzoneVolume volume = mock(OzoneVolume.class);
176+
when(volume.listBuckets(anyString())).thenThrow(
177+
new RuntimeException(new OMException("Volume Not Found", ResultCodes.VOLUME_NOT_FOUND)));
178+
return volume;
179+
}
180+
};
181+
182+
// Ensure we get an empty iterator
183+
assertFalse(endpointBase.listS3Buckets("prefix", volume -> { }).hasNext());
184+
}
140185
}

hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/exception/TestOS3Exceptions.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.apache.hadoop.ozone.s3.exception;
1919

20+
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
2021
import static org.junit.jupiter.api.Assertions.assertEquals;
2122

2223
import org.apache.hadoop.ozone.web.utils.OzoneUtils;
@@ -48,9 +49,19 @@ public void testOS3Exceptions() {
4849
assertEquals(expected, val);
4950
}
5051

52+
/**
53+
* AWS S3 returns HTTP 400 Bad Request for ExpiredToken (not 403).
54+
*/
55+
@Test
56+
public void testExpiredTokenUsesBadRequestHttpStatus() {
57+
assertEquals(HTTP_BAD_REQUEST, S3ErrorTable.EXPIRED_TOKEN.getHttpCode());
58+
final OS3Exception fromTable = S3ErrorTable.newError(S3ErrorTable.EXPIRED_TOKEN, "resource");
59+
assertEquals(HTTP_BAD_REQUEST, fromTable.getHttpCode());
60+
}
61+
5162
@Test
5263
public void testOS3ExceptionWithToken0() {
53-
OS3Exception ex = new OS3Exception("ExpiredToken", "The provided token has expired.", 403);
64+
OS3Exception ex = new OS3Exception("ExpiredToken", "The provided token has expired.", 400);
5465
ex = S3ErrorTable.newError(ex, "resource");
5566
ex.setRequestId(OzoneUtils.getRequestID());
5667
ex.setHostId(OzoneUtils.getRequestID());

0 commit comments

Comments
 (0)