diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java index 887ab004944d..a5d14126e0f6 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Blob.java @@ -184,7 +184,6 @@ public static BlobSourceOption decryptionKey(String key) { * Returns an option for blob's billing user project. This option is used only if the blob's * bucket has requester_pays flag enabled. */ - @GcpLaunchStage.Alpha public static BlobSourceOption userProject(String userProject) { return new BlobSourceOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -403,6 +402,13 @@ Builder setCustomerEncryption(CustomerEncryption customerEncryption) { return this; } + @GcpLaunchStage.Beta + @Override + Builder setKmsKeyName(String kmsKeyName) { + infoBuilder.setKmsKeyName(kmsKeyName); + return this; + } + @Override public Blob build() { return new Blob(storage, infoBuilder); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java index a903500455de..386daa63390a 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BlobInfo.java @@ -24,6 +24,7 @@ import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.StorageObject; import com.google.api.services.storage.model.StorageObject.Owner; +import com.google.cloud.GcpLaunchStage; import com.google.cloud.storage.Blob.Builder; import com.google.common.base.Function; import com.google.common.base.MoreObjects; @@ -83,6 +84,7 @@ public StorageObject apply(BlobInfo blobInfo) { private final Integer componentCount; private final boolean isDirectory; private final CustomerEncryption customerEncryption; + private final String kmsKeyName; /** * This class is meant for internal use only. Users are discouraged from using this class. @@ -266,6 +268,13 @@ public abstract static class Builder { abstract Builder setCustomerEncryption(CustomerEncryption customerEncryption); + /** + * + * Sets the blob's kmsKeyName. + */ + @GcpLaunchStage.Beta + abstract Builder setKmsKeyName(String kmsKeyName); + /** * Creates a {@code BlobInfo} object. */ @@ -298,6 +307,7 @@ static final class BuilderImpl extends Builder { private Boolean isDirectory; private CustomerEncryption customerEncryption; private StorageClass storageClass; + private String kmsKeyName; BuilderImpl(BlobId blobId) { this.blobId = blobId; @@ -328,6 +338,7 @@ static final class BuilderImpl extends Builder { createTime = blobInfo.createTime; isDirectory = blobInfo.isDirectory; storageClass = blobInfo.storageClass; + kmsKeyName = blobInfo.kmsKeyName; } @Override @@ -475,6 +486,13 @@ Builder setCustomerEncryption(CustomerEncryption customerEncryption) { return this; } + @GcpLaunchStage.Beta + @Override + Builder setKmsKeyName(String kmsKeyName) { + this.kmsKeyName = kmsKeyName; + return this; + } + @Override public BlobInfo build() { checkNotNull(blobId); @@ -507,6 +525,7 @@ public BlobInfo build() { createTime = builder.createTime; isDirectory = firstNonNull(builder.isDirectory, Boolean.FALSE); storageClass = builder.storageClass; + kmsKeyName = builder.kmsKeyName; } /** @@ -737,6 +756,14 @@ public StorageClass getStorageClass() { return storageClass; } + /** + * Returns the Cloud KMS key used to encrypt the blob, if any. + */ + @GcpLaunchStage.Beta + public String getKmsKeyName() { + return kmsKeyName; + } + /** * Returns a builder for the current blob. */ @@ -809,6 +836,8 @@ public ObjectAccessControl apply(Acl acl) { if (customerEncryption != null) { storageObject.setCustomerEncryption(customerEncryption.toPb()); } + + storageObject.setKmsKeyName(kmsKeyName); storageObject.setMetadata(pbMetadata); storageObject.setCacheControl(cacheControl); storageObject.setContentEncoding(contentEncoding); @@ -939,6 +968,9 @@ public Acl apply(ObjectAccessControl objectAccessControl) { if (storageObject.getStorageClass() != null) { builder.setStorageClass(StorageClass.valueOf(storageObject.getStorageClass())); } + if (storageObject.getKmsKeyName() != null) { + builder.setKmsKeyName(storageObject.getKmsKeyName()); + } return builder.build(); } } diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java index b4855e96a411..179e8a5cbfef 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java @@ -118,7 +118,6 @@ public static BucketSourceOption metagenerationNotMatch() { * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BucketSourceOption userProject(String userProject) { return new BucketSourceOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -188,6 +187,9 @@ private Tuple toTargetOption(BlobInfo blobIn case CUSTOMER_SUPPLIED_KEY: return Tuple.of(blobInfo, Storage.BlobTargetOption.encryptionKey((String) getValue())); + case KMS_KEY_NAME: + return Tuple.of(blobInfo, + Storage.BlobTargetOption.kmsKeyName((String) getValue())); case USER_PROJECT: return Tuple.of(blobInfo, Storage.BlobTargetOption.userProject((String) getValue())); @@ -267,11 +269,20 @@ public static BlobTargetOption encryptionKey(String key) { return new BlobTargetOption(StorageRpc.Option.CUSTOMER_SUPPLIED_KEY, key); } + /** + * Returns an option to set a customer-managed KMS key for server-side encryption of the + * blob. + * + * @param kmsKeyName the KMS key resource id + */ + public static BlobTargetOption kmsKeyName(String kmsKeyName) { + return new BlobTargetOption(StorageRpc.Option.KMS_KEY_NAME, kmsKeyName); + } + /** * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BlobTargetOption userProject(String userProject) { return new BlobTargetOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -345,6 +356,9 @@ private Tuple toWriteOption(BlobInfo blobInfo case CUSTOMER_SUPPLIED_KEY: return Tuple.of(blobInfo, Storage.BlobWriteOption.encryptionKey((String) value)); + case KMS_KEY_NAME: + return Tuple.of(blobInfo, + Storage.BlobWriteOption.kmsKeyName((String) value)); case USER_PROJECT: return Tuple.of(blobInfo, Storage.BlobWriteOption.userProject((String) value)); default: @@ -468,7 +482,6 @@ public static BlobWriteOption encryptionKey(String key) { * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BlobWriteOption userProject(String userProject) { return new BlobWriteOption(Storage.BlobWriteOption.Option.USER_PROJECT, userProject); } @@ -615,6 +628,13 @@ public Builder setLabels(Map labels) { return this; } + @GcpLaunchStage.Beta + @Override + public Builder setDefaultKmsKeyName(String defaultKmsKeyName) { + infoBuilder.setDefaultKmsKeyName(defaultKmsKeyName); + return this; + } + @Override public Bucket build() { return new Bucket(storage, infoBuilder); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java index 82a98a75ad82..6e0b3e2c38e9 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java @@ -25,11 +25,13 @@ import com.google.api.client.util.DateTime; import com.google.api.services.storage.model.*; import com.google.api.services.storage.model.Bucket; +import com.google.api.services.storage.model.Bucket.Encryption; import com.google.api.services.storage.model.Bucket.Lifecycle; import com.google.api.services.storage.model.Bucket.Lifecycle.Rule; import com.google.api.services.storage.model.Bucket.Owner; import com.google.api.services.storage.model.Bucket.Versioning; import com.google.api.services.storage.model.Bucket.Website; +import com.google.cloud.GcpLaunchStage; import com.google.cloud.storage.Acl.Entity; import com.google.common.base.Function; import com.google.common.base.MoreObjects; @@ -85,6 +87,7 @@ public com.google.api.services.storage.model.Bucket apply(BucketInfo bucketInfo) private final String location; private final StorageClass storageClass; private final Map labels; + private final String defaultKmsKeyName; /** * Base class for bucket's delete rules. Allows to configure automatic deletion of blobs and blobs @@ -423,6 +426,12 @@ public abstract static class Builder { */ public abstract Builder setLabels(Map labels); + /** + * Sets the default Cloud KMS key name for this bucket. + */ + @GcpLaunchStage.Beta + public abstract Builder setDefaultKmsKeyName(String defaultKmsKeyName); + /** * Creates a {@code BucketInfo} object. */ @@ -449,6 +458,7 @@ static final class BuilderImpl extends Builder { private List acl; private List defaultAcl; private Map labels; + private String defaultKmsKeyName; BuilderImpl(String name) { this.name = name; @@ -473,6 +483,7 @@ static final class BuilderImpl extends Builder { deleteRules = bucketInfo.deleteRules; labels = bucketInfo.labels; requesterPays = bucketInfo.requesterPays; + defaultKmsKeyName = bucketInfo.defaultKmsKeyName; } @Override @@ -584,6 +595,14 @@ public Builder setLabels(Map labels) { return this; } + @GcpLaunchStage.Beta + @Override + public Builder setDefaultKmsKeyName(String defaultKmsKeyName) { + this.defaultKmsKeyName = defaultKmsKeyName != null + ? defaultKmsKeyName : Data.nullOf(String.class); + return this; + } + @Override public BucketInfo build() { checkNotNull(name); @@ -610,6 +629,7 @@ public BucketInfo build() { deleteRules = builder.deleteRules; labels = builder.labels; requesterPays = builder.requesterPays; + defaultKmsKeyName = builder.defaultKmsKeyName; } /** @@ -762,6 +782,14 @@ public Map getLabels() { return labels; } + /** + * Returns the default Cloud KMS key to be applied to newly inserted objects in this bucket. + */ + @GcpLaunchStage.Beta + public String getDefaultKmsKeyName() { + return defaultKmsKeyName; + } + /** * Returns a builder for the current bucket. */ @@ -857,7 +885,9 @@ public Rule apply(DeleteRule deleteRule) { if (labels != null) { bucketPb.setLabels(labels); } - + if (defaultKmsKeyName != null) { + bucketPb.setEncryption(new Encryption().setDefaultKmsKeyName(defaultKmsKeyName)); + } return bucketPb; } @@ -945,6 +975,10 @@ public DeleteRule apply(Rule rule) { if (billing != null) { builder.setRequesterPays(billing.getRequesterPays()); } + Encryption encryption = bucketPb.getEncryption(); + if (encryption != null && encryption.getDefaultKmsKeyName() != null && !encryption.getDefaultKmsKeyName().isEmpty()) { + builder.setDefaultKmsKeyName(encryption.getDefaultKmsKeyName()); + } return builder.build(); } } diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 728438145493..b49d48c70b08 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -19,7 +19,6 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; -import com.google.api.core.BetaApi; import com.google.api.gax.paging.Page; import com.google.auth.ServiceAccountSigner; import com.google.auth.ServiceAccountSigner.SigningException; @@ -94,7 +93,8 @@ enum BucketField implements FieldSelector { CORS("cors"), STORAGE_CLASS("storageClass"), ETAG("etag"), - @GcpLaunchStage.Alpha + @GcpLaunchStage.Beta + ENCRYPTION("encryption"), BILLING("billing"); static final List REQUIRED_FIELDS = ImmutableList.of(NAME); @@ -136,6 +136,8 @@ enum BlobField implements FieldSelector { SIZE("size"), STORAGE_CLASS("storageClass"), TIME_DELETED("timeDeleted"), + @GcpLaunchStage.Beta + KMS_KEY_NAME("kmsKeyName"), UPDATED("updated"); static final List REQUIRED_FIELDS = ImmutableList.of(BUCKET, NAME); @@ -203,7 +205,6 @@ public static BucketTargetOption metagenerationNotMatch() { * Returns an option to define the billing user project. This option is required by buckets with * `requester_pays` flag enabled to assign operation costs. */ - @GcpLaunchStage.Alpha public static BucketTargetOption userProject(String userProject) { return new BucketTargetOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -240,7 +241,6 @@ public static BucketSourceOption metagenerationNotMatch(long metageneration) { * Returns an option for bucket's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BucketSourceOption userProject(String userProject) { return new BucketSourceOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -281,7 +281,6 @@ public static BucketGetOption metagenerationNotMatch(long metageneration) { * Returns an option for bucket's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BucketGetOption userProject(String userProject) { return new BucketGetOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -372,7 +371,6 @@ public static BlobTargetOption encryptionKey(Key key) { * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BlobTargetOption userProject(String userProject) { return new BlobTargetOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -387,6 +385,14 @@ public static BlobTargetOption encryptionKey(String key) { return new BlobTargetOption(StorageRpc.Option.CUSTOMER_SUPPLIED_KEY, key); } + /** + * Returns an option to set a customer-managed key for server-side encryption of the blob. + */ + @GcpLaunchStage.Beta + public static BlobTargetOption kmsKeyName(String kmsKeyName) { + return new BlobTargetOption(StorageRpc.Option.KMS_KEY_NAME, kmsKeyName); + } + static Tuple convert(BlobInfo info, BlobWriteOption... options) { BlobInfo.Builder infoBuilder = info.toBuilder().setCrc32c(null).setMd5(null); List targetOptions = Lists.newArrayListWithCapacity(options.length); @@ -420,7 +426,7 @@ class BlobWriteOption implements Serializable { enum Option { PREDEFINED_ACL, IF_GENERATION_MATCH, IF_GENERATION_NOT_MATCH, IF_METAGENERATION_MATCH, - IF_METAGENERATION_NOT_MATCH, IF_MD5_MATCH, IF_CRC32C_MATCH, CUSTOMER_SUPPLIED_KEY, USER_PROJECT; + IF_METAGENERATION_NOT_MATCH, IF_MD5_MATCH, IF_CRC32C_MATCH, CUSTOMER_SUPPLIED_KEY, KMS_KEY_NAME, USER_PROJECT; StorageRpc.Option toRpcOption() { return StorageRpc.Option.valueOf(this.name()); @@ -538,11 +544,21 @@ public static BlobWriteOption encryptionKey(String key) { return new BlobWriteOption(Option.CUSTOMER_SUPPLIED_KEY, key); } + /** + * Returns an option to set a customer-managed KMS key for server-side encryption of the + * blob. + * + * @param kmsKeyName the KMS key resource id + */ + @GcpLaunchStage.Beta + public static BlobWriteOption kmsKeyName(String kmsKeyName) { + return new BlobWriteOption(Option.KMS_KEY_NAME, kmsKeyName); + } + /** * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BlobWriteOption userProject(String userProject) { return new BlobWriteOption(Option.USER_PROJECT, userProject); } @@ -636,7 +652,6 @@ public static BlobSourceOption decryptionKey(String key) { * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BlobSourceOption userProject(String userProject) { return new BlobSourceOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -726,7 +741,6 @@ public static BlobGetOption fields(BlobField... fields) { * Returns an option for blob's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BlobGetOption userProject(String userProject) { return new BlobGetOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -769,7 +783,6 @@ public static BucketListOption prefix(String prefix) { * Returns an option for bucket's billing user project. This option is only used by the buckets with * 'requester_pays' flag. */ - @GcpLaunchStage.Alpha public static BucketListOption userProject(String userProject) { return new BucketListOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -840,7 +853,6 @@ public static BlobListOption currentDirectory() { * * @param userProject projectId of the billing user project. */ - @GcpLaunchStage.Alpha public static BlobListOption userProject(String userProject) { return new BlobListOption(StorageRpc.Option.USER_PROJECT, userProject); } @@ -2587,8 +2599,6 @@ public static Builder newBuilder() { * @param options extra parameters to apply to this operation * @throws StorageException upon failure */ - @BetaApi - @GcpLaunchStage.Alpha Policy getIamPolicy(String bucket, BucketSourceOption... options); /** @@ -2612,8 +2622,6 @@ public static Builder newBuilder() { * @param options extra parameters to apply to this operation * @throws StorageException upon failure */ - @BetaApi - @GcpLaunchStage.Alpha Policy setIamPolicy(String bucket, Policy policy, BucketSourceOption... options); /** @@ -2637,8 +2645,6 @@ public static Builder newBuilder() { * @param options extra parameters to apply to this operation * @throws StorageException upon failure */ - @BetaApi - @GcpLaunchStage.Alpha List testIamPermissions(String bucket, List permissions, BucketSourceOption... options); /** diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index b0a71387690a..1ce683e7aab4 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -285,6 +285,7 @@ public StorageObject create(StorageObject storageObject, final InputStream conte .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(options)) .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(options)) .setUserProject(Option.USER_PROJECT.getString(options)) + .setKmsKeyName(Option.KMS_KEY_NAME.getString(options)) .execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); @@ -789,6 +790,7 @@ private RewriteResponse rewrite(RewriteRequest req, String token) { if (userProject == null) { userProject = Option.USER_PROJECT.getString(req.targetOptions); } + String kmsKeyName = Option.KMS_KEY_NAME.getString(req.targetOptions); Long maxBytesRewrittenPerCall = req.megabytesRewrittenPerCall != null ? req.megabytesRewrittenPerCall * MEGABYTE : null; @@ -808,7 +810,8 @@ private RewriteResponse rewrite(RewriteRequest req, String token) { .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(req.targetOptions)) .setIfGenerationMatch(Option.IF_GENERATION_MATCH.getLong(req.targetOptions)) .setIfGenerationNotMatch(Option.IF_GENERATION_NOT_MATCH.getLong(req.targetOptions)) - .setUserProject(userProject); + .setUserProject(userProject) + .setDestinationKmsKeyName(kmsKeyName); HttpHeaders requestHeaders = rewrite.getRequestHeaders(); setEncryptionHeaders(requestHeaders, SOURCE_ENCRYPTION_KEY_PREFIX, req.sourceOptions); setEncryptionHeaders(requestHeaders, ENCRYPTION_KEY_PREFIX, req.targetOptions); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java index e8645b3eb384..780d8b864cfd 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java @@ -25,6 +25,7 @@ import com.google.api.services.storage.model.ServiceAccount; import com.google.api.services.storage.model.StorageObject; import com.google.api.services.storage.model.TestIamPermissionsResponse; +import com.google.cloud.GcpLaunchStage; import com.google.cloud.ServiceRpc; import com.google.cloud.Tuple; import com.google.cloud.storage.StorageException; @@ -55,7 +56,9 @@ enum Option { VERSIONS("versions"), FIELDS("fields"), CUSTOMER_SUPPLIED_KEY("customerSuppliedKey"), - USER_PROJECT("userProject"); + USER_PROJECT("userProject"), + @GcpLaunchStage.Beta + KMS_KEY_NAME("kmsKeyName"); private final String value; diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java index 16accd0e4a93..460bdb1ea811 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobInfoTest.java @@ -66,6 +66,7 @@ public class BlobInfoTest { private static final String KEY_SHA256 = "keySha"; private static final CustomerEncryption CUSTOMER_ENCRYPTION = new CustomerEncryption(ENCRYPTION_ALGORITHM, KEY_SHA256); + private static final String KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; private static final StorageClass STORAGE_CLASS = StorageClass.COLDLINE; private static final BlobInfo BLOB_INFO = BlobInfo.newBuilder("b", "n", GENERATION) @@ -91,6 +92,7 @@ public class BlobInfoTest { .setUpdateTime(UPDATE_TIME) .setCreateTime(CREATE_TIME) .setStorageClass(STORAGE_CLASS) + .setKmsKeyName(KMS_KEY_NAME) .build(); private static final BlobInfo DIRECTORY_INFO = BlobInfo.newBuilder("b", "n/") .setSize(0L) @@ -153,6 +155,7 @@ public void testBuilder() { assertEquals(UPDATE_TIME, BLOB_INFO.getUpdateTime()); assertEquals(CREATE_TIME, BLOB_INFO.getCreateTime()); assertEquals(STORAGE_CLASS, BLOB_INFO.getStorageClass()); + assertEquals(KMS_KEY_NAME, BLOB_INFO.getKmsKeyName()); assertFalse(BLOB_INFO.isDirectory()); assertEquals("b", DIRECTORY_INFO.getBucket()); assertEquals("n/", DIRECTORY_INFO.getName()); @@ -208,6 +211,7 @@ private void compareBlobs(BlobInfo expected, BlobInfo value) { assertEquals(expected.getSize(), value.getSize()); assertEquals(expected.getUpdateTime(), value.getUpdateTime()); assertEquals(expected.getStorageClass(), value.getStorageClass()); + assertEquals(expected.getKmsKeyName(), value.getKmsKeyName()); } private void compareCustomerEncryptions(CustomerEncryption expected, CustomerEncryption value) { @@ -255,6 +259,7 @@ public void testToPbAndFromPb() { assertEquals(0L, (long) blobInfo.getSize()); assertNull(blobInfo.getUpdateTime()); assertNull(blobInfo.getStorageClass()); + assertNull(blobInfo.getKmsKeyName()); assertTrue(blobInfo.isDirectory()); } diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java index b49d16fbc340..f1b52a526b71 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BlobTest.java @@ -91,6 +91,7 @@ public class BlobTest { private static final String KEY_SHA256 = "keySha"; private static final BlobInfo.CustomerEncryption CUSTOMER_ENCRYPTION = new BlobInfo.CustomerEncryption(ENCRYPTION_ALGORITHM, KEY_SHA256); + private static final String KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; private static final BlobInfo FULL_BLOB_INFO = BlobInfo.newBuilder("b", "n", GENERATION) .setAcl(ACLS) .setComponentCount(COMPONENT_COUNT) @@ -113,6 +114,7 @@ public class BlobTest { .setUpdateTime(UPDATE_TIME) .setCreateTime(CREATE_TIME) .setCustomerEncryption(CUSTOMER_ENCRYPTION) + .setKmsKeyName(KMS_KEY_NAME) .build(); private static final BlobInfo BLOB_INFO = BlobInfo.newBuilder("b", "n") .setMetageneration(42L) @@ -365,6 +367,18 @@ public void testWriterWithEncryptionKey() throws Exception { assertSame(channel, blob.writer(BlobWriteOption.encryptionKey(KEY))); } + @Test + public void testWriterWithKmsKeyName() throws Exception { + initializeExpectedBlob(2); + BlobWriteChannel channel = createMock(BlobWriteChannel.class); + expect(storage.getOptions()).andReturn(mockOptions); + expect(storage.writer(eq(expectedBlob), eq(BlobWriteOption.kmsKeyName(KMS_KEY_NAME)))) + .andReturn(channel); + replay(storage); + initializeBlob(); + assertSame(channel, blob.writer(BlobWriteOption.kmsKeyName(KMS_KEY_NAME))); + } + @Test public void testSignUrl() throws Exception { initializeExpectedBlob(2); @@ -457,6 +471,7 @@ public void testBuilder() { .setCrc32c(CRC32) .setCreateTime(CREATE_TIME) .setCustomerEncryption(CUSTOMER_ENCRYPTION) + .setKmsKeyName(KMS_KEY_NAME) .setDeleteTime(DELETE_TIME) .setEtag(ETAG) .setGeneratedId(GENERATED_ID) @@ -511,6 +526,7 @@ public void testBuilder() { assertNull(blob.getCrc32c()); assertNull(blob.getCreateTime()); assertNull(blob.getCustomerEncryption()); + assertNull(blob.getKmsKeyName()); assertNull(blob.getDeleteTime()); assertNull(blob.getEtag()); assertNull(blob.getGeneratedId()); diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java index 49010ef181db..d31dd0246e0b 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketInfoTest.java @@ -60,6 +60,7 @@ public class BucketInfoTest { private static final String NOT_FOUND_PAGE = "error.html"; private static final String LOCATION = "ASIA"; private static final StorageClass STORAGE_CLASS = StorageClass.STANDARD; + private static final String DEFAULT_KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; private static final Boolean VERSIONING_ENABLED = true; private static final Map BUCKET_LABELS = ImmutableMap.of("label1", "value1"); private static final Boolean REQUESTER_PAYS = true; @@ -81,6 +82,7 @@ public class BucketInfoTest { .setVersioningEnabled(VERSIONING_ENABLED) .setLabels(BUCKET_LABELS) .setRequesterPays(REQUESTER_PAYS) + .setDefaultKmsKeyName(DEFAULT_KMS_KEY_NAME) .build(); @Test @@ -122,6 +124,7 @@ public void testBuilder() { assertEquals(NOT_FOUND_PAGE, BUCKET_INFO.getNotFoundPage()); assertEquals(LOCATION, BUCKET_INFO.getLocation()); assertEquals(STORAGE_CLASS, BUCKET_INFO.getStorageClass()); + assertEquals(DEFAULT_KMS_KEY_NAME, BUCKET_INFO.getDefaultKmsKeyName()); assertEquals(VERSIONING_ENABLED, BUCKET_INFO.versioningEnabled()); assertEquals(BUCKET_LABELS, BUCKET_INFO.getLabels()); assertEquals(REQUESTER_PAYS, BUCKET_INFO.requesterPays()); @@ -151,6 +154,7 @@ private void compareBuckets(BucketInfo expected, BucketInfo value) { assertEquals(expected.getNotFoundPage(), value.getNotFoundPage()); assertEquals(expected.getLocation(), value.getLocation()); assertEquals(expected.getStorageClass(), value.getStorageClass()); + assertEquals(expected.getDefaultKmsKeyName(), value.getDefaultKmsKeyName()); assertEquals(expected.versioningEnabled(), value.versioningEnabled()); assertEquals(expected.getLabels(), value.getLabels()); assertEquals(expected.requesterPays(), value.requesterPays()); diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java index 2b46316f3614..707a9f3c7565 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/BucketTest.java @@ -77,6 +77,7 @@ public class BucketTest { private static final String NOT_FOUND_PAGE = "error.html"; private static final String LOCATION = "ASIA"; private static final StorageClass STORAGE_CLASS = StorageClass.STANDARD; + private static final String DEFAULT_KMS_KEY_NAME = "projects/p/locations/kr-loc/keyRings/kr/cryptoKeys/key"; private static final Boolean VERSIONING_ENABLED = true; private static final Map BUCKET_LABELS = ImmutableMap.of("label1", "value1"); private static final Boolean REQUESTER_PAYS = true; @@ -99,6 +100,7 @@ public class BucketTest { .setVersioningEnabled(VERSIONING_ENABLED) .setLabels(BUCKET_LABELS) .setRequesterPays(REQUESTER_PAYS) + .setDefaultKmsKeyName(DEFAULT_KMS_KEY_NAME) .build(); private static final BucketInfo BUCKET_INFO = BucketInfo.newBuilder("b").setMetageneration(42L).build(); @@ -364,6 +366,22 @@ public void testCreateWithEncryptionKey() throws Exception { assertEquals(expectedBlob, blob); } + @Test + public void testCreateWithKmsKeyName() throws Exception { + initializeExpectedBucket(5); + BlobInfo info = BlobInfo.newBuilder(BlobId.of("b", "n")).setContentType(CONTENT_TYPE).build(); + Blob expectedBlob = new Blob(serviceMockReturnsOptions, new BlobInfo.BuilderImpl(info)); + byte[] content = {0xD, 0xE, 0xA, 0xD}; + expect(storage.getOptions()).andReturn(mockOptions); + expect(storage.create(info, content, Storage.BlobTargetOption.kmsKeyName(DEFAULT_KMS_KEY_NAME))) + .andReturn(expectedBlob); + replay(storage); + initializeBucket(); + Blob blob = + bucket.create("n", content, CONTENT_TYPE, Bucket.BlobTargetOption.kmsKeyName(DEFAULT_KMS_KEY_NAME)); + assertEquals(expectedBlob, blob); + } + @Test public void testCreateNotExists() throws Exception { initializeExpectedBucket(5); @@ -675,6 +693,7 @@ public void testBuilder() { .setVersioningEnabled(VERSIONING_ENABLED) .setLabels(BUCKET_LABELS) .setRequesterPays(REQUESTER_PAYS) + .setDefaultKmsKeyName(DEFAULT_KMS_KEY_NAME) .build(); assertEquals("b", bucket.getName()); assertEquals(ACLS, bucket.getAcl()); @@ -694,6 +713,7 @@ public void testBuilder() { assertEquals(VERSIONING_ENABLED, bucket.versioningEnabled()); assertEquals(BUCKET_LABELS, bucket.getLabels()); assertEquals(REQUESTER_PAYS, bucket.requesterPays()); + assertEquals(DEFAULT_KMS_KEY_NAME, bucket.getDefaultKmsKeyName()); assertEquals(storage.getOptions(), bucket.getStorage().getOptions()); } } diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java index dbab8bc4ad83..9fea3887c464 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java @@ -102,6 +102,8 @@ public class StorageImplTest { private static final String BASE64_KEY = "JVzfVl8NLD9FjedFuStegjRfES5ll5zc59CIXw572OA="; private static final Key KEY = new SecretKeySpec(BaseEncoding.base64().decode(BASE64_KEY), "AES256"); + private static final String KMS_KEY_NAME = + "projects/gcloud-devel/locations/us/keyRings/gcs_kms_key_ring_us/cryptoKeys/key"; // BucketInfo objects private static final BucketInfo BUCKET_INFO1 = @@ -245,6 +247,9 @@ public class StorageImplTest { private static final Map ENCRYPTION_KEY_OPTIONS = ImmutableMap.of(StorageRpc.Option.CUSTOMER_SUPPLIED_KEY, BASE64_KEY); + // Customer managed encryption key options + private static final Map KMS_KEY_NAME_OPTIONS = + ImmutableMap.of(StorageRpc.Option.KMS_KEY_NAME, KMS_KEY_NAME); // IAM policies private static final String POLICY_ETAG1 = "CAE="; private static final String POLICY_ETAG2 = "CAI="; @@ -561,6 +566,40 @@ public void testCreateBlobWithEncryptionKey() throws IOException { assertEquals(-1, byteStream.read(streamBytes)); } + @Test + public void testCreateBlobWithKmsKeyName() throws IOException { + Capture capturedStream = Capture.newInstance(); + EasyMock.expect( + storageRpcMock.create( + EasyMock.eq( + BLOB_INFO1 + .toBuilder() + .setMd5(CONTENT_MD5) + .setCrc32c(CONTENT_CRC32C) + .build() + .toPb()), + EasyMock.capture(capturedStream), + EasyMock.eq(KMS_KEY_NAME_OPTIONS))) + .andReturn(BLOB_INFO1.toPb()) + .times(2); + EasyMock.replay(storageRpcMock); + initializeService(); + Blob blob = storage.create(BLOB_INFO1, BLOB_CONTENT, BlobTargetOption.kmsKeyName(KMS_KEY_NAME)); + assertEquals(expectedBlob1, blob); + ByteArrayInputStream byteStream = capturedStream.getValue(); + byte[] streamBytes = new byte[BLOB_CONTENT.length]; + assertEquals(BLOB_CONTENT.length, byteStream.read(streamBytes)); + assertArrayEquals(BLOB_CONTENT, streamBytes); + assertEquals(-1, byteStream.read(streamBytes)); + blob = storage.create(BLOB_INFO1, BLOB_CONTENT, BlobTargetOption.kmsKeyName(KMS_KEY_NAME)); + assertEquals(expectedBlob1, blob); + byteStream = capturedStream.getValue(); + streamBytes = new byte[BLOB_CONTENT.length]; + assertEquals(BLOB_CONTENT.length, byteStream.read(streamBytes)); + assertArrayEquals(BLOB_CONTENT, streamBytes); + assertEquals(-1, byteStream.read(streamBytes)); + } + @Test public void testCreateBlobFromStream() throws IOException { Capture capturedStream = Capture.newInstance(); @@ -1238,6 +1277,43 @@ public void testCopyWithEncryptionKey() { assertTrue(!writer.isDone()); } + @Test + public void testCopyFromEncryptionKeyToKmsKeyName() { + CopyRequest request = + Storage.CopyRequest.newBuilder() + .setSource(BLOB_INFO2.getBlobId()) + .setSourceOptions(BlobSourceOption.decryptionKey(KEY)) + .setTarget(BLOB_INFO1, BlobTargetOption.kmsKeyName(KMS_KEY_NAME)) + .build(); + StorageRpc.RewriteRequest rpcRequest = + new StorageRpc.RewriteRequest( + request.getSource().toPb(), + ENCRYPTION_KEY_OPTIONS, + true, + request.getTarget().toPb(), + KMS_KEY_NAME_OPTIONS, + null); + StorageRpc.RewriteResponse rpcResponse = + new StorageRpc.RewriteResponse(rpcRequest, null, 42L, false, "token", 21L); + EasyMock.expect(storageRpcMock.openRewrite(rpcRequest)).andReturn(rpcResponse).times(2); + EasyMock.replay(storageRpcMock); + initializeService(); + CopyWriter writer = storage.copy(request); + assertEquals(42L, writer.getBlobSize()); + assertEquals(21L, writer.getTotalBytesCopied()); + assertTrue(!writer.isDone()); + request = + Storage.CopyRequest.newBuilder() + .setSource(BLOB_INFO2.getBlobId()) + .setSourceOptions(BlobSourceOption.decryptionKey(BASE64_KEY)) + .setTarget(BLOB_INFO1, BlobTargetOption.kmsKeyName(KMS_KEY_NAME)) + .build(); + writer = storage.copy(request); + assertEquals(42L, writer.getBlobSize()); + assertEquals(21L, writer.getTotalBytesCopied()); + assertTrue(!writer.isDone()); + } + @Test public void testCopyWithOptionsFromBlobId() { CopyRequest request = @@ -1489,6 +1565,22 @@ public void testWriterWithEncryptionKey() { assertTrue(channel.isOpen()); } + @Test + public void testWriterWithKmsKeyName() { + BlobInfo info = BLOB_INFO1.toBuilder().setMd5(null).setCrc32c(null).build(); + EasyMock.expect(storageRpcMock.open(info.toPb(), KMS_KEY_NAME_OPTIONS)) + .andReturn("upload-id") + .times(2); + EasyMock.replay(storageRpcMock); + initializeService(); + WriteChannel channel = storage.writer(info, BlobWriteOption.kmsKeyName(KMS_KEY_NAME)); + assertNotNull(channel); + assertTrue(channel.isOpen()); + channel = storage.writer(info, BlobWriteOption.kmsKeyName(KMS_KEY_NAME)); + assertNotNull(channel); + assertTrue(channel.isOpen()); + } + @Test public void testSignUrl() throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index 9f6272b87bf2..09360efe2a4f 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -108,6 +108,8 @@ public class ITStorageTest { .decode("H4sIAAAAAAAAAPNIzcnJV3DPz0/PSVVwzskvTVEILskvSkxPVQQA/LySchsAAAA="); private static final Map BUCKET_LABELS = ImmutableMap.of("label1", "value1"); private static final String SERVICE_ACCOUNT_EMAIL = "gcloud-devel@gs-project-accounts.iam.gserviceaccount.com"; + private static final String KMS_KEY_NAME_1 = "projects/gcloud-devel/locations/us/keyRings/gcs_kms_key_ring_us/cryptoKeys/key"; + private static final String KMS_KEY_NAME_2 = "projects/gcloud-devel/locations/us/keyRings/gcs_kms_key_ring_us/cryptoKeys/key2"; @BeforeClass public static void beforeClass() throws NoSuchAlgorithmException, InvalidKeySpecException { @@ -177,6 +179,36 @@ public void testGetBucketEmptyFields() { assertNull(remoteBucket.getSelfLink()); } + @Test + public void testClearBucketDefaultKmsKeyName() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket remoteBucket = storage.create(BucketInfo.newBuilder(bucketName) + .setDefaultKmsKeyName(KMS_KEY_NAME_1).setLocation("US").build()); + + try { + assertEquals(KMS_KEY_NAME_1, remoteBucket.getDefaultKmsKeyName()); + Bucket updatedBucket = remoteBucket.toBuilder().setDefaultKmsKeyName(null).build().update(); + assertNull(updatedBucket.getDefaultKmsKeyName()); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + + @Test + public void testUpdateBucketDefaultKmsKeyName() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket remoteBucket = storage.create(BucketInfo.newBuilder(bucketName) + .setDefaultKmsKeyName(KMS_KEY_NAME_1).setLocation("US").build()); + + try { + assertEquals(KMS_KEY_NAME_1, remoteBucket.getDefaultKmsKeyName()); + Bucket updatedBucket = remoteBucket.toBuilder().setDefaultKmsKeyName(KMS_KEY_NAME_2).build().update(); + assertEquals(KMS_KEY_NAME_2, updatedBucket.getDefaultKmsKeyName()); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + @Test public void testCreateBlob() { String blobName = "test-create-blob"; @@ -204,6 +236,56 @@ public void testCreateBlobWithEncryptionKey() { assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); } + @Test + public void testCreateBlobWithKmsKeyName() { + String blobName = "test-create-with-kms-key-name-blob"; + BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).build(); + Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1)); + assertNotNull(remoteBlob); + assertEquals(blob.getBucket(), remoteBlob.getBucket()); + assertEquals(blob.getName(), remoteBlob.getName()); + assertNotNull(remoteBlob.getKmsKeyName()); + assertTrue(remoteBlob.getKmsKeyName().startsWith(KMS_KEY_NAME_1)); + byte[] readBytes = storage.readAllBytes(BUCKET, blobName); + assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); + } + + @Test + public void testCreateBlobWithKmsKeyNameAndCustomerSuppliedKey() { + try { + String blobName = "test-create-with-kms-key-name-blob"; + BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName).build(); + storage.create(blob, BLOB_BYTE_CONTENT, Storage.BlobTargetOption.encryptionKey(KEY), + Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1)); + fail("StorageException was expected"); // can't supply both. + } catch (StorageException ex) { + // expected + } + } + + @Test + public void testCreateBlobWithDefaultKmsKeyName() throws ExecutionException, InterruptedException { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket bucket = storage.create(BucketInfo.newBuilder(bucketName) + .setDefaultKmsKeyName(KMS_KEY_NAME_1).setLocation("US").build()); + assertEquals(bucket.getDefaultKmsKeyName(), KMS_KEY_NAME_1); + + try { + String blobName = "test-create-with-default-kms-key-name-blob"; + BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); + Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT); + assertNotNull(remoteBlob); + assertEquals(blob.getBucket(), remoteBlob.getBucket()); + assertEquals(blob.getName(), remoteBlob.getName()); + assertNotNull(remoteBlob.getKmsKeyName()); + assertTrue(remoteBlob.getKmsKeyName().startsWith(KMS_KEY_NAME_1)); + byte[] readBytes = storage.readAllBytes(bucketName, blobName); + assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); + } finally { + RemoteStorageHelper.forceDelete(storage, bucketName, 5, TimeUnit.SECONDS); + } + } + @Test public void testCreateEmptyBlob() { String blobName = "test-create-empty-blob"; @@ -287,6 +369,20 @@ public void testGetBlobSelectedFields() { assertNull(remoteBlob.getContentType()); } + @Test + public void testGetBlobKmsKeyNameField() { + String blobName = "test-get-selected-kms-key-name-field-blob"; + BlobInfo blob = BlobInfo.newBuilder(BUCKET, blobName) + .setContentType(CONTENT_TYPE) + .build(); + assertNotNull(storage.create(blob, Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1))); + Blob remoteBlob = storage.get(blob.getBlobId(), Storage.BlobGetOption.fields( + BlobField.KMS_KEY_NAME)); + assertEquals(blob.getBlobId(), remoteBlob.getBlobId()); + assertTrue(remoteBlob.getKmsKeyName().startsWith(KMS_KEY_NAME_1)); + assertNull(remoteBlob.getContentType()); + } + @Test public void testGetBlobAllSelectedFields() { String blobName = "test-get-all-selected-fields-blob"; @@ -373,6 +469,42 @@ public void testListBlobsSelectedFields() throws InterruptedException { } } + @Test(timeout = 5000) + public void testListBlobsKmsKeySelectedFields() throws InterruptedException { + String[] blobNames = {"test-list-blobs-selected-field-kms-key-name-blob1", + "test-list-blobs-selected-field-kms-key-name-blob2"}; + BlobInfo blob1 = BlobInfo.newBuilder(BUCKET, blobNames[0]) + .setContentType(CONTENT_TYPE) + .build(); + BlobInfo blob2 = BlobInfo.newBuilder(BUCKET, blobNames[1]) + .setContentType(CONTENT_TYPE) + .build(); + Blob remoteBlob1 = storage.create(blob1, Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1)); + Blob remoteBlob2 = storage.create(blob2, Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1)); + assertNotNull(remoteBlob1); + assertNotNull(remoteBlob2); + Page page = storage.list(BUCKET, + Storage.BlobListOption.prefix("test-list-blobs-selected-field-kms-key-name-blob"), + Storage.BlobListOption.fields(BlobField.KMS_KEY_NAME)); + // Listing blobs is eventually consistent, we loop until the list is of the expected size. The + // test fails if timeout is reached. + while (Iterators.size(page.iterateAll().iterator()) != 2) { + Thread.sleep(500); + page = storage.list(BUCKET, + Storage.BlobListOption.prefix("test-list-blobs-selected-field-kms-key-name-blob"), + Storage.BlobListOption.fields(BlobField.KMS_KEY_NAME)); + } + Set blobSet = ImmutableSet.of(blobNames[0], blobNames[1]); + Iterator iterator = page.iterateAll().iterator(); + while (iterator.hasNext()) { + Blob remoteBlob = iterator.next(); + assertEquals(BUCKET, remoteBlob.getBucket()); + assertTrue(blobSet.contains(remoteBlob.getName())); + assertTrue(remoteBlob.getKmsKeyName().startsWith(KMS_KEY_NAME_1)); + assertNull(remoteBlob.getContentType()); + } + } + @Test(timeout = 5000) public void testListBlobsEmptySelectedFields() throws InterruptedException { String[] blobNames = {"test-list-blobs-empty-selected-fields-blob1", @@ -806,6 +938,63 @@ public void testCopyBlobWithEncryptionKeys() { assertTrue(storage.delete(BUCKET, targetBlobName)); } + @Test + public void testRotateFromCustomerEncryptionToKmsKey() { + String sourceBlobName = "test-copy-blob-encryption-key-source"; + BlobId source = BlobId.of(BUCKET, sourceBlobName); + ImmutableMap metadata = ImmutableMap.of("k", "v"); + Blob remoteBlob = storage.create(BlobInfo.newBuilder(source).build(), BLOB_BYTE_CONTENT, + Storage.BlobTargetOption.encryptionKey(KEY)); + assertNotNull(remoteBlob); + String targetBlobName = "test-copy-blob-kms-key-target"; + BlobInfo target = BlobInfo.newBuilder(BUCKET, targetBlobName) + .setContentType(CONTENT_TYPE) + .setMetadata(metadata) + .build(); + Storage.CopyRequest req = Storage.CopyRequest.newBuilder() + .setSource(source) + .setSourceOptions(Storage.BlobSourceOption.decryptionKey(BASE64_KEY)) + .setTarget(target, Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1)) + .build(); + CopyWriter copyWriter = storage.copy(req); + assertEquals(BUCKET, copyWriter.getResult().getBucket()); + assertEquals(targetBlobName, copyWriter.getResult().getName()); + assertEquals(CONTENT_TYPE, copyWriter.getResult().getContentType()); + assertNotNull(copyWriter.getResult().getKmsKeyName()); + assertTrue(copyWriter.getResult().getKmsKeyName().startsWith(KMS_KEY_NAME_1)); + assertArrayEquals(BLOB_BYTE_CONTENT, copyWriter.getResult().getContent()); + assertEquals(metadata, copyWriter.getResult().getMetadata()); + assertTrue(copyWriter.isDone()); + assertTrue(storage.delete(BUCKET, targetBlobName)); + } + + @Test + public void testRotateFromCustomerEncryptionToKmsKeyWithCustomerEncrytion() { + String sourceBlobName = "test-copy-blob-encryption-key-source"; + BlobId source = BlobId.of(BUCKET, sourceBlobName); + ImmutableMap metadata = ImmutableMap.of("k", "v"); + Blob remoteBlob = storage.create(BlobInfo.newBuilder(source).build(), BLOB_BYTE_CONTENT, + Storage.BlobTargetOption.encryptionKey(KEY)); + assertNotNull(remoteBlob); + String targetBlobName = "test-copy-blob-kms-key-target"; + BlobInfo target = BlobInfo.newBuilder(BUCKET, targetBlobName) + .setContentType(CONTENT_TYPE) + .setMetadata(metadata) + .build(); + try { + Storage.CopyRequest req = Storage.CopyRequest.newBuilder() + .setSource(source) + .setSourceOptions(Storage.BlobSourceOption.decryptionKey(BASE64_KEY)) + .setTarget(target, Storage.BlobTargetOption.encryptionKey(KEY), + Storage.BlobTargetOption.kmsKeyName(KMS_KEY_NAME_1)) + .build(); + storage.copy(req); + fail("StorageException was expected"); + } catch (StorageException ex) { + // expected + } + } + @Test public void testCopyBlobUpdateMetadata() { String sourceBlobName = "test-copy-blob-update-metadata-source"; @@ -1670,6 +1859,29 @@ public void testListBucketRequesterPaysFails() throws InterruptedException { } } + @Test + public void testListBucketDefaultKmsKeyName() throws InterruptedException { + Bucket remoteBucket = storage.get(BUCKET, Storage.BucketGetOption.fields(BucketField.ENCRYPTION)); + assertNull(remoteBucket.getDefaultKmsKeyName()); + remoteBucket = remoteBucket.toBuilder().setDefaultKmsKeyName(KMS_KEY_NAME_1).build().update(); + assertTrue(remoteBucket.getDefaultKmsKeyName().startsWith(KMS_KEY_NAME_1)); + Iterator bucketIterator = storage.list(Storage.BucketListOption.prefix(BUCKET), + Storage.BucketListOption.fields(BucketField.ENCRYPTION)).iterateAll().iterator(); + while (!bucketIterator.hasNext()) { + Thread.sleep(500); + bucketIterator = storage.list(Storage.BucketListOption.prefix(BUCKET), + Storage.BucketListOption.fields(BucketField.ENCRYPTION)).iterateAll().iterator(); + } + while (bucketIterator.hasNext()) { + Bucket bucket = bucketIterator.next(); + assertTrue(bucket.getName().startsWith(BUCKET)); + assertNotNull(bucket.getDefaultKmsKeyName()); + assertTrue(bucket.getDefaultKmsKeyName().startsWith(KMS_KEY_NAME_1)); + assertNull(bucket.getCreateTime()); + assertNull(bucket.getSelfLink()); + } + } + @Test public void testGetServiceAccount() throws InterruptedException { String projectId = remoteStorageHelper.getOptions().getProjectId(); diff --git a/google-cloud-clients/pom.xml b/google-cloud-clients/pom.xml index d33a07e6cfc3..0c81c971a006 100644 --- a/google-cloud-clients/pom.xml +++ b/google-cloud-clients/pom.xml @@ -213,7 +213,7 @@ com.google.apis google-api-services-storage - v1-rev114-1.23.0 + v1-rev131-1.23.0 com.google.apis