Skip to content

Commit

Permalink
feat: add support of public access prevention (#636)
Browse files Browse the repository at this point in the history
Co-authored-by: BenWhitehead <BenWhitehead@users.noreply.github.com>
Co-authored-by: Frank Natividad <franknatividad@google.com>
Co-authored-by: Frank Natividad <frankyn@users.noreply.github.com>
  • Loading branch information
4 people committed Jun 28, 2021
1 parent 99138a4 commit 3d1e482
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 3 deletions.
Expand Up @@ -105,17 +105,56 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo)

private static final Logger log = Logger.getLogger(BucketInfo.class.getName());

/**
* Public Access Prevention enum with expected values.
*
* @see <a
* href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
*/
public enum PublicAccessPrevention {
ENFORCED("enforced"),
/** Default value for Public Access Prevention */
UNSPECIFIED("unspecified"),
/**
* If the api returns a value that isn't defined in {@link PublicAccessPrevention} this value
* will be returned.
*/
UNKNOWN(null);

private final String value;

PublicAccessPrevention(String value) {
this.value = value;
}

public String getValue() {
return value;
}

public static PublicAccessPrevention parse(String value) {
String upper = value.toUpperCase();
try {
return valueOf(upper);
} catch (IllegalArgumentException ignore) {
return UNKNOWN;
}
}
}

/**
* The Bucket's IAM Configuration.
*
* @see <a href="https://cloud.google.com/storage/docs/uniform-bucket-level-access">uniform
* bucket-level access</a>
* @see <a
* href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
*/
public static class IamConfiguration implements Serializable {
private static final long serialVersionUID = -8671736104909424616L;

private Boolean isUniformBucketLevelAccessEnabled;
private Long uniformBucketLevelAccessLockedTime;
private final Boolean isUniformBucketLevelAccessEnabled;
private final Long uniformBucketLevelAccessLockedTime;
private final PublicAccessPrevention publicAccessPrevention;

@Override
public boolean equals(Object o) {
Expand All @@ -129,12 +168,16 @@ public boolean equals(Object o) {

@Override
public int hashCode() {
return Objects.hash(isUniformBucketLevelAccessEnabled, uniformBucketLevelAccessLockedTime);
return Objects.hash(
isUniformBucketLevelAccessEnabled,
uniformBucketLevelAccessLockedTime,
publicAccessPrevention);
}

private IamConfiguration(Builder builder) {
this.isUniformBucketLevelAccessEnabled = builder.isUniformBucketLevelAccessEnabled;
this.uniformBucketLevelAccessLockedTime = builder.uniformBucketLevelAccessLockedTime;
this.publicAccessPrevention = builder.publicAccessPrevention;
}

public static Builder newBuilder() {
Expand All @@ -145,6 +188,7 @@ public Builder toBuilder() {
Builder builder = new Builder();
builder.isUniformBucketLevelAccessEnabled = isUniformBucketLevelAccessEnabled;
builder.uniformBucketLevelAccessLockedTime = uniformBucketLevelAccessLockedTime;
builder.publicAccessPrevention = publicAccessPrevention;
return builder;
}

Expand All @@ -168,6 +212,11 @@ public Long getUniformBucketLevelAccessLockedTime() {
return uniformBucketLevelAccessLockedTime;
}

/** Returns the Public Access Prevention. * */
public PublicAccessPrevention getPublicAccessPrevention() {
return publicAccessPrevention;
}

Bucket.IamConfiguration toPb() {
Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();

Expand All @@ -180,6 +229,8 @@ Bucket.IamConfiguration toPb() {
: new DateTime(uniformBucketLevelAccessLockedTime));

iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
iamConfiguration.setPublicAccessPrevention(
publicAccessPrevention == null ? null : publicAccessPrevention.getValue());

return iamConfiguration;
}
Expand All @@ -188,17 +239,25 @@ static IamConfiguration fromPb(Bucket.IamConfiguration iamConfiguration) {
Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
iamConfiguration.getUniformBucketLevelAccess();
DateTime lockedTime = uniformBucketLevelAccess.getLockedTime();
String publicAccessPrevention = iamConfiguration.getPublicAccessPrevention();

PublicAccessPrevention publicAccessPreventionValue = null;
if (publicAccessPrevention != null) {
publicAccessPreventionValue = PublicAccessPrevention.parse(publicAccessPrevention);
}

return newBuilder()
.setIsUniformBucketLevelAccessEnabled(uniformBucketLevelAccess.getEnabled())
.setUniformBucketLevelAccessLockedTime(lockedTime == null ? null : lockedTime.getValue())
.setPublicAccessPrevention(publicAccessPreventionValue)
.build();
}

/** Builder for {@code IamConfiguration} */
public static class Builder {
private Boolean isUniformBucketLevelAccessEnabled;
private Long uniformBucketLevelAccessLockedTime;
private PublicAccessPrevention publicAccessPrevention;

/** Deprecated in favor of setIsUniformBucketLevelAccessEnabled(). */
@Deprecated
Expand Down Expand Up @@ -239,6 +298,18 @@ Builder setUniformBucketLevelAccessLockedTime(Long uniformBucketLevelAccessLocke
return this;
}

/**
* Sets the bucket's Public Access Prevention configuration. Currently supported options are
* {@link PublicAccessPrevention#UNSPECIFIED} or {@link PublicAccessPrevention#ENFORCED}
*
* @see <a
* href="https://cloud.google.com/storage/docs/public-access-prevention">public-access-prevention</a>
*/
public Builder setPublicAccessPrevention(PublicAccessPrevention publicAccessPrevention) {
this.publicAccessPrevention = publicAccessPrevention;
return this;
}

/** Builds an {@code IamConfiguration} object */
public IamConfiguration build() {
return new IamConfiguration(this);
Expand Down
Expand Up @@ -18,10 +18,13 @@

import static com.google.cloud.storage.Acl.Project.ProjectRole.VIEWERS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import com.google.api.client.json.JsonGenerator;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.DateTime;
import com.google.api.services.storage.model.Bucket;
import com.google.api.services.storage.model.Bucket.Lifecycle;
Expand All @@ -33,14 +36,18 @@
import com.google.cloud.storage.BucketInfo.CreatedBeforeDeleteRule;
import com.google.cloud.storage.BucketInfo.DeleteRule;
import com.google.cloud.storage.BucketInfo.DeleteRule.Type;
import com.google.cloud.storage.BucketInfo.IamConfiguration;
import com.google.cloud.storage.BucketInfo.IsLiveDeleteRule;
import com.google.cloud.storage.BucketInfo.LifecycleRule;
import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction;
import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition;
import com.google.cloud.storage.BucketInfo.NumNewerVersionsDeleteRule;
import com.google.cloud.storage.BucketInfo.PublicAccessPrevention;
import com.google.cloud.storage.BucketInfo.RawDeleteRule;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -79,6 +86,7 @@ public class BucketInfoTest {
BucketInfo.IamConfiguration.newBuilder()
.setIsUniformBucketLevelAccessEnabled(true)
.setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
.setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
.build();
private static final BucketInfo.Logging LOGGING =
BucketInfo.Logging.newBuilder()
Expand Down Expand Up @@ -363,11 +371,45 @@ public void testIamConfiguration() {
BucketInfo.IamConfiguration.newBuilder()
.setIsUniformBucketLevelAccessEnabled(true)
.setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
.setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
.build()
.toPb();

assertEquals(Boolean.TRUE, iamConfiguration.getUniformBucketLevelAccess().getEnabled());
assertNotNull(iamConfiguration.getUniformBucketLevelAccess().getLockedTime());
assertEquals(
BucketInfo.PublicAccessPrevention.ENFORCED.getValue(),
iamConfiguration.getPublicAccessPrevention());
}

@Test
public void testPublicAccessPrevention_ensureAbsentWhenUnknown() throws IOException {
StringWriter stringWriter = new StringWriter();
JsonGenerator jsonGenerator =
JacksonFactory.getDefaultInstance().createJsonGenerator(stringWriter);

jsonGenerator.serialize(
BucketInfo.IamConfiguration.newBuilder()
.setIsUniformBucketLevelAccessEnabled(true)
.setUniformBucketLevelAccessLockedTime(System.currentTimeMillis())
.setPublicAccessPrevention(PublicAccessPrevention.UNKNOWN)
.build()
.toPb());
jsonGenerator.flush();

assertFalse(stringWriter.getBuffer().toString().contains("publicAccessPrevention"));
}

@Test
public void testPapValueOfIamConfiguration() {
Bucket.IamConfiguration iamConfiguration = new Bucket.IamConfiguration();
Bucket.IamConfiguration.UniformBucketLevelAccess uniformBucketLevelAccess =
new Bucket.IamConfiguration.UniformBucketLevelAccess();
iamConfiguration.setUniformBucketLevelAccess(uniformBucketLevelAccess);
iamConfiguration.setPublicAccessPrevention("random-string");
IamConfiguration fromPb = IamConfiguration.fromPb(iamConfiguration);

assertEquals(PublicAccessPrevention.UNKNOWN, fromPb.getPublicAccessPrevention());
}

@Test
Expand Down
Expand Up @@ -3271,6 +3271,151 @@ public void testEnableAndDisableUniformBucketLevelAccessOnExistingBucket() throw
}
}

private Bucket generatePublicAccessPreventionBucket(String bucketName, boolean enforced) {
return storage.create(
Bucket.newBuilder(bucketName)
.setIamConfiguration(
BucketInfo.IamConfiguration.newBuilder()
.setPublicAccessPrevention(
enforced
? BucketInfo.PublicAccessPrevention.ENFORCED
: BucketInfo.PublicAccessPrevention.UNSPECIFIED)
.build())
.build());
}

@Test
public void testEnforcedPublicAccessPreventionOnBucket() throws Exception {
String papBucket = RemoteStorageHelper.generateBucketName();
try {
Bucket bucket = generatePublicAccessPreventionBucket(papBucket, true);
// Making bucket public should fail.
try {
storage.setIamPolicy(
papBucket,
Policy.newBuilder()
.setVersion(3)
.setBindings(
ImmutableList.<com.google.cloud.Binding>of(
com.google.cloud.Binding.newBuilder()
.setRole("roles/storage.objectViewer")
.addMembers("allUsers")
.build()))
.build());
fail("pap: expected adding allUsers policy to bucket should fail");
} catch (StorageException storageException) {
// Creating a bucket with roles/storage.objectViewer is not
// allowed when publicAccessPrevention is enabled.
assertEquals(storageException.getCode(), 412);
}

// Making object public via ACL should fail.
try {
// Create a public object
bucket.create(
"pap-test-object",
"".getBytes(),
Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ));
fail("pap: expected adding allUsers ACL to object should fail");
} catch (StorageException storageException) {
// Creating an object with allUsers roles/storage.viewer permission
// is not allowed. When Public Access Prevention is enabled.
assertEquals(storageException.getCode(), 412);
}
} finally {
RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
}
}

@Test
public void testUnspecifiedPublicAccessPreventionOnBucket() throws Exception {
String papBucket = RemoteStorageHelper.generateBucketName();
try {
Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false);

// Now, making object public or making bucket public should succeed.
try {
// Create a public object
bucket.create(
"pap-test-object",
"".getBytes(),
Bucket.BlobTargetOption.predefinedAcl(Storage.PredefinedAcl.PUBLIC_READ));
} catch (StorageException storageException) {
fail("pap: expected adding allUsers ACL to object to succeed");
}

// Now, making bucket public should succeed.
try {
storage.setIamPolicy(
papBucket,
Policy.newBuilder()
.setVersion(3)
.setBindings(
ImmutableList.<com.google.cloud.Binding>of(
com.google.cloud.Binding.newBuilder()
.setRole("roles/storage.objectViewer")
.addMembers("allUsers")
.build()))
.build());
} catch (StorageException storageException) {
fail("pap: expected adding allUsers policy to bucket to succeed");
}
} finally {
RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
}
}

@Test
public void testUBLAWithPublicAccessPreventionOnBucket() throws Exception {
String papBucket = RemoteStorageHelper.generateBucketName();
try {
Bucket bucket = generatePublicAccessPreventionBucket(papBucket, false);
assertEquals(
bucket.getIamConfiguration().getPublicAccessPrevention(),
BucketInfo.PublicAccessPrevention.UNSPECIFIED);
assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());

// Update PAP setting to ENFORCED and should not affect UBLA setting.
bucket
.toBuilder()
.setIamConfiguration(
bucket
.getIamConfiguration()
.toBuilder()
.setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED)
.build())
.build()
.update();
bucket = storage.get(papBucket, Storage.BucketGetOption.fields(BucketField.IAMCONFIGURATION));
assertEquals(
bucket.getIamConfiguration().getPublicAccessPrevention(),
BucketInfo.PublicAccessPrevention.ENFORCED);
assertFalse(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
assertFalse(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());

// Updating UBLA should not affect PAP setting.
bucket =
bucket
.toBuilder()
.setIamConfiguration(
bucket
.getIamConfiguration()
.toBuilder()
.setIsUniformBucketLevelAccessEnabled(true)
.build())
.build()
.update();
assertTrue(bucket.getIamConfiguration().isUniformBucketLevelAccessEnabled());
assertTrue(bucket.getIamConfiguration().isBucketPolicyOnlyEnabled());
assertEquals(
bucket.getIamConfiguration().getPublicAccessPrevention(),
BucketInfo.PublicAccessPrevention.ENFORCED);
} finally {
RemoteStorageHelper.forceDelete(storage, papBucket, 1, TimeUnit.MINUTES);
}
}

@Test
public void testUploadUsingSignedURL() throws Exception {
String blobName = "test-signed-url-upload";
Expand Down

0 comments on commit 3d1e482

Please sign in to comment.