diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java index 42e8140dcb87..1218b78d1dc2 100644 --- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v1/AbstractS3SDKV1Tests.java @@ -53,12 +53,14 @@ import com.amazonaws.services.s3.model.CopyPartResult; import com.amazonaws.services.s3.model.CreateBucketRequest; import com.amazonaws.services.s3.model.DeleteBucketTaggingConfigurationRequest; +import com.amazonaws.services.s3.model.DeletePublicAccessBlockRequest; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import com.amazonaws.services.s3.model.GetBucketLifecycleConfigurationRequest; import com.amazonaws.services.s3.model.GetBucketTaggingConfigurationRequest; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.GetObjectTaggingRequest; import com.amazonaws.services.s3.model.GetObjectTaggingResult; +import com.amazonaws.services.s3.model.GetPublicAccessBlockRequest; import com.amazonaws.services.s3.model.Grantee; import com.amazonaws.services.s3.model.InitiateMultipartUploadRequest; import com.amazonaws.services.s3.model.InitiateMultipartUploadResult; @@ -79,6 +81,7 @@ import com.amazonaws.services.s3.model.PartListing; import com.amazonaws.services.s3.model.PartSummary; import com.amazonaws.services.s3.model.Permission; +import com.amazonaws.services.s3.model.PublicAccessBlockConfiguration; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.PutObjectResult; import com.amazonaws.services.s3.model.S3Object; @@ -88,6 +91,7 @@ import com.amazonaws.services.s3.model.SetBucketTaggingConfigurationRequest; import com.amazonaws.services.s3.model.SetObjectAclRequest; import com.amazonaws.services.s3.model.SetObjectTaggingRequest; +import com.amazonaws.services.s3.model.SetPublicAccessBlockRequest; import com.amazonaws.services.s3.model.Tag; import com.amazonaws.services.s3.model.TagSet; import com.amazonaws.services.s3.model.UploadPartRequest; @@ -485,6 +489,37 @@ public void testDeleteBucket() { assertFalse(s3Client.doesBucketExistV2(bucketName)); } + @Test + public void testPublicAccessBlockIsNotImplemented() { + final String bucketName = getBucketName(); + s3Client.createBucket(bucketName); + + AmazonServiceException getException = assertThrows(AmazonServiceException.class, + () -> s3Client.getPublicAccessBlock( + new GetPublicAccessBlockRequest().withBucketName(bucketName))); + assertEquals(HttpURLConnection.HTTP_NOT_IMPLEMENTED, getException.getStatusCode()); + assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), getException.getErrorCode()); + + AmazonServiceException putException = assertThrows(AmazonServiceException.class, + () -> s3Client.setPublicAccessBlock( + new SetPublicAccessBlockRequest() + .withBucketName(bucketName) + .withPublicAccessBlockConfiguration( + new PublicAccessBlockConfiguration() + .withBlockPublicAcls(true)))); + assertEquals(HttpURLConnection.HTTP_NOT_IMPLEMENTED, putException.getStatusCode()); + assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), putException.getErrorCode()); + + AmazonServiceException deleteException = assertThrows(AmazonServiceException.class, + () -> s3Client.deletePublicAccessBlock( + new DeletePublicAccessBlockRequest().withBucketName(bucketName))); + assertEquals(HttpURLConnection.HTTP_NOT_IMPLEMENTED, deleteException.getStatusCode()); + assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), deleteException.getErrorCode()); + assertTrue(s3Client.doesBucketExistV2(bucketName)); + + s3Client.deleteBucket(bucketName); + } + @Test public void testDeleteBucketNotExist() { final String bucketName = getBucketName(); diff --git a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java index bfcf5b741bc3..e17b6cf52471 100644 --- a/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java +++ b/hadoop-ozone/integration-test-s3/src/test/java/org/apache/hadoop/ozone/s3/awssdk/v2/AbstractS3SDKV2Tests.java @@ -277,6 +277,35 @@ public void testCreateBucketAlreadyExistsDifferentOwner() throws IOException { exception.awsErrorDetails().errorCode()); } + @Test + public void testPublicAccessBlockIsNotImplemented() { + final String bucketName = getBucketName(); + s3Client.createBucket(b -> b.bucket(bucketName)); + + S3Exception getException = assertThrows(S3Exception.class, + () -> s3Client.getPublicAccessBlock(b -> b.bucket(bucketName))); + assertEquals(HttpURLConnection.HTTP_NOT_IMPLEMENTED, getException.statusCode()); + assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), + getException.awsErrorDetails().errorCode()); + + S3Exception putException = assertThrows(S3Exception.class, + () -> s3Client.putPublicAccessBlock(b -> b + .bucket(bucketName) + .publicAccessBlockConfiguration(c -> c.blockPublicAcls(true)))); + assertEquals(HttpURLConnection.HTTP_NOT_IMPLEMENTED, putException.statusCode()); + assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), + putException.awsErrorDetails().errorCode()); + + S3Exception deleteException = assertThrows(S3Exception.class, + () -> s3Client.deletePublicAccessBlock(b -> b.bucket(bucketName))); + assertEquals(HttpURLConnection.HTTP_NOT_IMPLEMENTED, deleteException.statusCode()); + assertEquals(S3ErrorTable.NOT_IMPLEMENTED.getCode(), + deleteException.awsErrorDetails().errorCode()); + assertDoesNotThrow(() -> s3Client.headBucket(b -> b.bucket(bucketName))); + + s3Client.deleteBucket(b -> b.bucket(bucketName)); + } + @Test public void testPutObject() { final String bucketName = getBucketName(); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java index f215cd6cb0ff..5243afba3bf0 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/audit/S3GAction.java @@ -35,6 +35,9 @@ public enum S3GAction implements AuditAction { GET_BUCKET_LIFECYCLE, PUT_BUCKET_LIFECYCLE, DELETE_BUCKET_LIFECYCLE, + GET_UNSUPPORTED_BUCKET_SUBRESOURCE, + PUT_UNSUPPORTED_BUCKET_SUBRESOURCE, + DELETE_UNSUPPORTED_BUCKET_SUBRESOURCE, //RootEndpoint LIST_S3_BUCKETS, 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 3d4cc4d6ad5a..9db79b2b3d20 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 @@ -32,18 +32,15 @@ * Implements PUT (create bucket) and DELETE operations when no * subresource query parameters are present. * - * This handler processes bucket-level requests that do not target - * specific subresources (such as {@code ?acl}, {@code ?uploads}, - * {@code ?delete} or {@code ?tagging}), which are handled by dedicated handlers. + * This handler processes bucket-level requests that do not target a + * subresource handled by another handler. * * This handler extends EndpointBase to inherit all required functionality * (configuration, headers, request context, audit logging, metrics, etc.). */ public class BucketCrudHandler extends BucketOperationHandler { - /** - * Handle only plain PUT bucket (create bucket), not subresources. - */ + /** Handle only plain bucket creation and deletion, not subresources. */ private boolean shouldHandle() { return queryParams().get(QueryParams.ACL) == null && queryParams().get(QueryParams.UPLOADS) == null 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 c833d5f22f43..99b6036b119d 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 @@ -453,6 +453,7 @@ protected void init() { .add(new ListMultipartUploadsHandler()) .add(new BucketTaggingHandler()) .add(new BucketLifecycleHandler()) + .add(new UnsupportedBucketSubresourceHandler()) .add(new BucketCrudHandler()) .add(this) .build(); diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/UnsupportedBucketSubresourceHandler.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/UnsupportedBucketSubresourceHandler.java new file mode 100644 index 000000000000..800cc656b642 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/endpoint/UnsupportedBucketSubresourceHandler.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.ozone.s3.endpoint; + +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.NOT_IMPLEMENTED; +import static org.apache.hadoop.ozone.s3.exception.S3ErrorTable.newError; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Set; +import javax.ws.rs.core.Response; +import org.apache.hadoop.ozone.audit.S3GAction; +import org.apache.hadoop.ozone.s3.exception.OS3Exception; +import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams; + +/** Rejects unsupported bucket subresource operations. */ +class UnsupportedBucketSubresourceHandler extends BucketOperationHandler { + + private static final Set UNSUPPORTED_SUBRESOURCES = Set.of( + QueryParams.PUBLIC_ACCESS_BLOCK); + + private String findUnsupportedSubresource() { + for (String subresource : UNSUPPORTED_SUBRESOURCES) { + if (queryParams().get(subresource) != null) { + return subresource; + } + } + return null; + } + + @Override + Response handleGetRequest(S3RequestContext context, String bucketName) + throws IOException, OS3Exception { + String subresource = findUnsupportedSubresource(); + if (subresource == null) { + return null; + } + + context.setAction(S3GAction.GET_UNSUPPORTED_BUCKET_SUBRESOURCE); + throw newError(NOT_IMPLEMENTED, subresource); + } + + @Override + Response handlePutRequest(S3RequestContext context, String bucketName, + InputStream body) throws IOException, OS3Exception { + String subresource = findUnsupportedSubresource(); + if (subresource == null) { + return null; + } + + context.setAction(S3GAction.PUT_UNSUPPORTED_BUCKET_SUBRESOURCE); + throw newError(NOT_IMPLEMENTED, subresource); + } + + @Override + Response handleDeleteRequest(S3RequestContext context, String bucketName) + throws IOException, OS3Exception { + String subresource = findUnsupportedSubresource(); + if (subresource == null) { + return null; + } + + context.setAction(S3GAction.DELETE_UNSUPPORTED_BUCKET_SUBRESOURCE); + throw newError(NOT_IMPLEMENTED, subresource); + } +} diff --git a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java index f75653ad098b..5488fc154b2d 100644 --- a/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java +++ b/hadoop-ozone/s3gateway/src/main/java/org/apache/hadoop/ozone/s3/util/S3Consts.java @@ -172,6 +172,7 @@ public static final class QueryParams { public static final String PART_NUMBER = "partNumber"; public static final String PART_NUMBER_MARKER = "part-number-marker"; public static final String PREFIX = "prefix"; + public static final String PUBLIC_ACCESS_BLOCK = "publicAccessBlock"; public static final String START_AFTER = "start-after"; public static final String TAGGING = "tagging"; // GetObjectTorrent is not implemented diff --git a/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUnsupportedBucketSubresource.java b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUnsupportedBucketSubresource.java new file mode 100644 index 000000000000..d444acd2cee2 --- /dev/null +++ b/hadoop-ozone/s3gateway/src/test/java/org/apache/hadoop/ozone/s3/endpoint/TestUnsupportedBucketSubresource.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hadoop.ozone.s3.endpoint; + +import static org.apache.hadoop.ozone.s3.endpoint.EndpointTestUtils.assertErrorResponse; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.io.IOException; +import org.apache.hadoop.ozone.OzoneConsts; +import org.apache.hadoop.ozone.client.ObjectStore; +import org.apache.hadoop.ozone.client.OzoneClient; +import org.apache.hadoop.ozone.client.OzoneClientStub; +import org.apache.hadoop.ozone.s3.exception.S3ErrorTable; +import org.apache.hadoop.ozone.s3.util.S3Consts.QueryParams; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** Tests for unsupported bucket subresource operations. */ +public class TestUnsupportedBucketSubresource { + + private static final String BUCKET_NAME = OzoneConsts.BUCKET; + private ObjectStore objectStore; + private BucketEndpoint bucketEndpoint; + + @BeforeEach + public void setup() throws IOException { + OzoneClient client = new OzoneClientStub(); + objectStore = client.getObjectStore(); + objectStore.createS3Bucket(BUCKET_NAME); + + bucketEndpoint = EndpointBuilder.newBucketEndpointBuilder() + .setClient(client) + .build(); + bucketEndpoint.queryParamsForTest().set(QueryParams.PUBLIC_ACCESS_BLOCK, ""); + } + + @Test + public void getPublicAccessBlockIsNotImplemented() { + assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, + () -> bucketEndpoint.get(BUCKET_NAME)); + assertBucketExists(); + } + + @Test + public void putPublicAccessBlockIsNotImplemented() { + assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, + () -> bucketEndpoint.put(BUCKET_NAME, null)); + assertBucketExists(); + } + + @Test + public void deletePublicAccessBlockIsNotImplemented() { + assertErrorResponse(S3ErrorTable.NOT_IMPLEMENTED, + () -> bucketEndpoint.delete(BUCKET_NAME)); + assertBucketExists(); + } + + private void assertBucketExists() { + assertDoesNotThrow(() -> objectStore.getS3Bucket(BUCKET_NAME)); + } +}