Skip to content
This repository was archived by the owner on Aug 6, 2025. It is now read-only.

Commit acbd18b

Browse files
committed
JCLOUDS-1644: Create AWS S3 buckets with ownership and public access block
AWS changed the defaults when creating buckets to prevent public-read and other canned ACLs. Background: https://stackoverflow.com/a/76102067/2800111
1 parent bdfac92 commit acbd18b

File tree

5 files changed

+207
-0
lines changed

5 files changed

+207
-0
lines changed

apis/s3/src/main/java/org/jclouds/s3/S3Client.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,10 @@
6666
import org.jclouds.s3.binders.BindIterableAsPayloadToDeleteRequest;
6767
import org.jclouds.s3.binders.BindNoBucketLoggingToXmlPayload;
6868
import org.jclouds.s3.binders.BindObjectMetadataToRequest;
69+
import org.jclouds.s3.binders.BindOwnershipControlsToXMLPayload;
6970
import org.jclouds.s3.binders.BindPartIdsAndETagsToRequest;
7071
import org.jclouds.s3.binders.BindPayerToXmlPayload;
72+
import org.jclouds.s3.binders.BindPublicAccessBlockConfigurationToXMLPayload;
7173
import org.jclouds.s3.binders.BindS3ObjectMetadataToRequest;
7274
import org.jclouds.s3.domain.AccessControlList;
7375
import org.jclouds.s3.domain.BucketLogging;
@@ -79,6 +81,7 @@
7981
import org.jclouds.s3.domain.ListMultipartUploadsResponse;
8082
import org.jclouds.s3.domain.ObjectMetadata;
8183
import org.jclouds.s3.domain.Payer;
84+
import org.jclouds.s3.domain.PublicAccessBlockConfiguration;
8285
import org.jclouds.s3.domain.S3Object;
8386
import org.jclouds.s3.fallbacks.FalseIfBucketAlreadyOwnedByYouOrOperationAbortedWhenBucketExists;
8487
import org.jclouds.s3.filters.RequestAuthorizeSignature;
@@ -813,4 +816,19 @@ ListMultipartUploadsResponse listMultipartUploads(@Bucket @EndpointParam(parser
813816
@QueryParam("delimiter") @Nullable String delimiter, @QueryParam("max-uploads") @Nullable Integer maxUploads,
814817
@QueryParam("key-marker") @Nullable String keyMarker, @QueryParam("prefix") @Nullable String prefix,
815818
@QueryParam("upload-id-marker") @Nullable String uploadIdMarker);
819+
820+
@Named("PutBucketOwnershipControls")
821+
@PUT
822+
@Path("/")
823+
@QueryParams(keys = "ownershipControls")
824+
void putBucketOwnershipControls(@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName,
825+
// BucketOwnerPreferred | ObjectWriter | BucketOwnerEnforced
826+
@BinderParam(BindOwnershipControlsToXMLPayload.class) String objectOwnership);
827+
828+
@Named("PutPublicAccessBlock")
829+
@PUT
830+
@Path("/")
831+
@QueryParams(keys = "publicAccessBlock")
832+
void putPublicAccessBlock(@Bucket @EndpointParam(parser = AssignCorrectHostnameForBucket.class) @BinderParam(BindAsHostPrefixIfConfigured.class) @ParamValidators(BucketNameValidator.class) String bucketName,
833+
@BinderParam(BindPublicAccessBlockConfigurationToXMLPayload.class) PublicAccessBlockConfiguration configuration);
816834
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.jclouds.s3.binders;
18+
19+
import static org.jclouds.s3.binders.XMLHelper.asString;
20+
import static org.jclouds.s3.binders.XMLHelper.createDocument;
21+
import static org.jclouds.s3.binders.XMLHelper.elem;
22+
import static org.jclouds.s3.binders.XMLHelper.elemWithText;
23+
24+
import jakarta.inject.Singleton;
25+
import jakarta.ws.rs.core.MediaType;
26+
import javax.xml.parsers.FactoryConfigurationError;
27+
import javax.xml.parsers.ParserConfigurationException;
28+
import javax.xml.transform.TransformerException;
29+
30+
import org.jclouds.http.HttpRequest;
31+
import org.jclouds.rest.Binder;
32+
import org.jclouds.s3.reference.S3Constants;
33+
import org.w3c.dom.Document;
34+
import org.w3c.dom.Element;
35+
36+
import com.google.common.base.Throwables;
37+
38+
@Singleton
39+
public final class BindOwnershipControlsToXMLPayload implements Binder {
40+
@Override
41+
public <R extends HttpRequest> R bindToRequest(R request, Object payload) {
42+
String from = (String) payload;
43+
try {
44+
request.setPayload(generatePayload(from));
45+
request.getPayload().getContentMetadata().setContentType(MediaType.TEXT_XML);
46+
return request;
47+
} catch (Exception e) {
48+
Throwables.propagateIfPossible(e);
49+
throw new RuntimeException("error transforming acl: " + from, e);
50+
}
51+
}
52+
53+
protected String generatePayload(String objectOwnership)
54+
throws ParserConfigurationException, FactoryConfigurationError, TransformerException {
55+
Document document = createDocument();
56+
Element rootNode = elem(document, "OwnershipControls", document);
57+
rootNode.setAttribute("xmlns", S3Constants.S3_REST_API_XML_NAMESPACE);
58+
Element ruleNode = elem(rootNode, "Rule", document);
59+
elemWithText(ruleNode, "ObjectOwnership", objectOwnership, document);
60+
return asString(document);
61+
}
62+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.jclouds.s3.binders;
18+
19+
import static org.jclouds.s3.binders.XMLHelper.asString;
20+
import static org.jclouds.s3.binders.XMLHelper.createDocument;
21+
import static org.jclouds.s3.binders.XMLHelper.elem;
22+
import static org.jclouds.s3.binders.XMLHelper.elemWithText;
23+
24+
import jakarta.inject.Singleton;
25+
import jakarta.ws.rs.core.MediaType;
26+
import javax.xml.parsers.FactoryConfigurationError;
27+
import javax.xml.parsers.ParserConfigurationException;
28+
import javax.xml.transform.TransformerException;
29+
30+
import org.jclouds.http.HttpRequest;
31+
import org.jclouds.rest.Binder;
32+
import org.jclouds.s3.domain.PublicAccessBlockConfiguration;
33+
import org.jclouds.s3.reference.S3Constants;
34+
import org.w3c.dom.Document;
35+
import org.w3c.dom.Element;
36+
37+
import com.google.common.base.Throwables;
38+
39+
@Singleton
40+
public final class BindPublicAccessBlockConfigurationToXMLPayload implements Binder {
41+
@Override
42+
public <R extends HttpRequest> R bindToRequest(R request, Object payload) {
43+
PublicAccessBlockConfiguration configuration = (PublicAccessBlockConfiguration) payload;
44+
try {
45+
request.setPayload(generatePayload(configuration));
46+
request.getPayload().getContentMetadata().setContentType(MediaType.TEXT_XML);
47+
return request;
48+
} catch (Exception e) {
49+
Throwables.propagateIfPossible(e);
50+
throw new RuntimeException("error transforming configuration: " + configuration, e);
51+
}
52+
}
53+
54+
protected String generatePayload(PublicAccessBlockConfiguration configuration)
55+
throws ParserConfigurationException, FactoryConfigurationError, TransformerException {
56+
Document document = createDocument();
57+
Element rootNode = elem(document, "PublicAccessBlockConfiguration", document);
58+
rootNode.setAttribute("xmlns", S3Constants.S3_REST_API_XML_NAMESPACE);
59+
elemWithText(rootNode, "BlockPublicAcls", String.valueOf(configuration.blockPublicAcls()), document);
60+
elemWithText(rootNode, "IgnorePublicAcls", String.valueOf(configuration.ignorePublicAcls()), document);
61+
elemWithText(rootNode, "BlockPublicPolicy", String.valueOf(configuration.blockPublicPolicy()), document);
62+
elemWithText(rootNode, "RestrictPublicBuckets", String.valueOf(configuration.restrictPublicBuckets()), document);
63+
return asString(document);
64+
}
65+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.jclouds.s3.domain;
18+
19+
import com.google.auto.value.AutoValue;
20+
import com.google.common.annotations.Beta;
21+
22+
@AutoValue
23+
@Beta
24+
public abstract class PublicAccessBlockConfiguration {
25+
public abstract boolean blockPublicAcls();
26+
public abstract boolean ignorePublicAcls();
27+
public abstract boolean blockPublicPolicy();
28+
public abstract boolean restrictPublicBuckets();
29+
30+
public static PublicAccessBlockConfiguration create(boolean blockPublicAcls, boolean ignorePublicAcls, boolean blockPublicPolicy, boolean restrictPublicBuckets) {
31+
return new AutoValue_PublicAccessBlockConfiguration(blockPublicAcls, ignorePublicAcls, blockPublicPolicy, restrictPublicBuckets);
32+
}
33+
}

providers/aws-s3/src/main/java/org/jclouds/aws/s3/blobstore/AWSS3BlobStore.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import org.jclouds.aws.s3.blobstore.options.AWSS3PutOptions;
3030
import org.jclouds.blobstore.BlobStoreContext;
3131
import org.jclouds.blobstore.domain.Blob;
32+
import org.jclouds.blobstore.domain.ContainerAccess;
3233
import org.jclouds.blobstore.domain.PageSet;
3334
import org.jclouds.blobstore.domain.StorageMetadata;
3435
import org.jclouds.blobstore.functions.BlobToHttpGetOptions;
@@ -47,7 +48,9 @@
4748
import org.jclouds.s3.blobstore.functions.ObjectToBlob;
4849
import org.jclouds.s3.blobstore.functions.ObjectToBlobMetadata;
4950
import org.jclouds.s3.domain.BucketMetadata;
51+
import org.jclouds.s3.domain.CannedAccessPolicy;
5052
import org.jclouds.s3.domain.ObjectMetadata;
53+
import org.jclouds.s3.domain.PublicAccessBlockConfiguration;
5154

5255
import com.google.common.base.Function;
5356
import com.google.common.base.Supplier;
@@ -58,6 +61,7 @@
5861
public class AWSS3BlobStore extends S3BlobStore {
5962

6063
private final BlobToObject blob2Object;
64+
private final AWSS3Client awsSync;
6165

6266
@Inject
6367
AWSS3BlobStore(BlobStoreContext context, BlobUtils blobUtils, Supplier<Location> defaultLocation,
@@ -70,6 +74,7 @@ public class AWSS3BlobStore extends S3BlobStore {
7074
super(context, blobUtils, defaultLocation, locations, slicer, sync, convertBucketsToStorageMetadata,
7175
container2BucketListOptions, bucket2ResourceList, object2Blob, blob2ObjectGetOptions, blob2Object,
7276
blob2ObjectMetadata, object2BlobMd, fetchBlobMetadataProvider);
77+
this.awsSync = sync;
7378
this.blob2Object = blob2Object;
7479
}
7580

@@ -102,6 +107,30 @@ public boolean createContainerInLocation(Location location, String container,
102107
// JCLOUDS-334 for details.
103108
return false;
104109
}
110+
// AWS blocks creating buckets with public-read canned ACL by default since 25 April 2023. Instead create a bucket, override the block, and set the ACL.
111+
if (options.isPublicRead()) {
112+
boolean created = super.createContainerInLocation(location, container, new CreateContainerOptions());
113+
if (!created) {
114+
return false;
115+
}
116+
awsSync.putBucketOwnershipControls(container, "ObjectWriter");
117+
awsSync.putPublicAccessBlock(container, PublicAccessBlockConfiguration.create(
118+
/*blockPublicAcls=*/ false, /*ignorePublicAcls=*/ false, /*blockPublicPolicy=*/ false, /*restrictPublicBuckets=*/ false));
119+
awsSync.updateBucketCannedACL(container, CannedAccessPolicy.PUBLIC_READ);
120+
return true;
121+
}
105122
return super.createContainerInLocation(location, container, options);
106123
}
124+
125+
@Override
126+
public void setContainerAccess(String container, ContainerAccess access) {
127+
CannedAccessPolicy acl = CannedAccessPolicy.PRIVATE;
128+
if (access == ContainerAccess.PUBLIC_READ) {
129+
acl = CannedAccessPolicy.PUBLIC_READ;
130+
awsSync.putBucketOwnershipControls(container, "ObjectWriter");
131+
awsSync.putPublicAccessBlock(container, PublicAccessBlockConfiguration.create(
132+
/*blockPublicAcls=*/ false, /*ignorePublicAcls=*/ false, /*blockPublicPolicy=*/ false, /*restrictPublicBuckets=*/ false));
133+
}
134+
awsSync.updateBucketCannedACL(container, acl);
135+
}
107136
}

0 commit comments

Comments
 (0)