Skip to content

Commit 8338116

Browse files
feat!: customer-managed encryption keys for Spanner (#666)
* feat: add support for encrypted databases * fix: fix deps and clirr failures * tests: add additional tests for keys * tests: remove IT and add unit * fix: set null instead of default instance * fix: does not set encryption info if null Does not set encryption info in the request if it is null * fix: fixes dependencies * feature: adds support for encrypted backup Adds the possibility to set encryption config info in the creation of a backup. * feature: adds support for restoring encrypted dbs * Revert "tests: remove IT and add unit" This reverts commit cc19cf2. * fix: makes the setEncryptionConfigInfo public This is so a backup can be encrypted * feature: adds tests for cmek Adds tests for creating encrypted database, creating encrypted backups and restoring encrypted databases. * fix: removes keys after test finishes Destroy keys used in CMEK tests * fix: fixes clirr errors * fix: ignores failing cmek tests Ignores the failing CMEK tests until the backend support is enabled in production. * fix: uses wrapper encryption info for backups * fix: fixes clirr issues * fix: re-orders clirr issues * fix: addresses PR comments * test: fixes database admin client tests * chore: re-formats the code * chore: fixes clirr checks * tests: adds unit tests for domain classes Adds unit tests for EncryptionConfigInfo, EncryptionConfig, Backup and Restore. * chore: renames EncryptionConfigInfo Renames EncryptionConfigInfo to EncryptionConfig in order to mirror what is the protobuf definition. * tests: do not create a key on CMEK test Instead use an existing key and fails if the key is not present. * feat: allows multiple encryption configs Allows customer managed encryption for create databases (google default encryption is just nullifying the value here). Allows customer managed encryption, google default encryption and database encryption for create backups. Allows customer managed encryption, google default encryption and backup encryption for restore databases. * docs: adds java doc to Restore class * chore: refactors pom.xml Uses variables to define project id and instance id for running integration tests. * test: fixes cmek integration test * chore: fixes linting * Revert "chore: refactors pom.xml" This reverts commit d182b83. * test: unifies cmek backup and restore tests * chore: adds toString to encryption classes * docs: updates DatabaseInfo javadoc Co-authored-by: Knut Olav Løite <[email protected]> * docs: updates Restore javadocs Co-authored-by: Knut Olav Løite <[email protected]> * docs: updates DatabaseInfo javadocs Co-authored-by: Knut Olav Løite <[email protected]> * fix: addresses PR comments * tests: reformats Co-authored-by: Olav Loite <[email protected]>
1 parent 0837496 commit 8338116

29 files changed

+1614
-165
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@
319319
<className>com/google/cloud/spanner/Value</className>
320320
<method>java.util.List getNumericArray()</method>
321321
</difference>
322-
322+
323323
<!-- Async Connection API -->
324324
<difference>
325325
<differenceType>7012</differenceType>
@@ -406,7 +406,7 @@
406406
<className>com/google/cloud/spanner/AbstractLazyInitializer</className>
407407
<method>java.lang.Object initialize()</method>
408408
</difference>
409-
409+
410410
<!-- TransactionOptions and UpdateOptions -->
411411
<difference>
412412
<differenceType>7004</differenceType>
@@ -504,4 +504,71 @@
504504
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
505505
<method>com.google.api.gax.longrunning.OperationFuture createBackup(com.google.cloud.spanner.Backup)</method>
506506
</difference>
507+
508+
<!-- Support creating encrypted databases -->
509+
<difference>
510+
<differenceType>7004</differenceType>
511+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
512+
<method>com.google.api.gax.longrunning.OperationFuture createDatabase(java.lang.String, java.lang.String, java.lang.Iterable)</method>
513+
</difference>
514+
<difference>
515+
<differenceType>7004</differenceType>
516+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
517+
<method>com.google.api.gax.longrunning.OperationFuture createBackup(java.lang.String, java.lang.String, com.google.spanner.admin.database.v1.Backup)</method>
518+
</difference>
519+
<difference>
520+
<differenceType>7004</differenceType>
521+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
522+
<method>com.google.api.gax.longrunning.OperationFuture restoreDatabase(java.lang.String, java.lang.String, java.lang.String)</method>
523+
</difference>
524+
<difference>
525+
<differenceType>7004</differenceType>
526+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
527+
<method>com.google.api.gax.longrunning.OperationFuture createDatabase(java.lang.String, java.lang.String, java.lang.Iterable)</method>
528+
</difference>
529+
<difference>
530+
<differenceType>7004</differenceType>
531+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
532+
<method>com.google.api.gax.longrunning.OperationFuture createBackup(java.lang.String, java.lang.String, com.google.spanner.admin.database.v1.Backup)</method>
533+
</difference>
534+
<difference>
535+
<differenceType>7004</differenceType>
536+
<className>com/google/cloud/spanner/spi/v1/GapicSpannerRpc</className>
537+
<method>com.google.api.gax.longrunning.OperationFuture restoreDatabase(java.lang.String, java.lang.String, java.lang.String)</method>
538+
</difference>
539+
<difference>
540+
<differenceType>7012</differenceType>
541+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
542+
<method>com.google.api.gax.longrunning.OperationFuture createDatabase(com.google.cloud.spanner.Database, java.lang.Iterable)</method>
543+
</difference>
544+
<difference>
545+
<differenceType>7012</differenceType>
546+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
547+
<method>com.google.api.gax.longrunning.OperationFuture createBackup(com.google.cloud.spanner.Backup)</method>
548+
</difference>
549+
<difference>
550+
<differenceType>7012</differenceType>
551+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
552+
<method>com.google.api.gax.longrunning.OperationFuture restoreDatabase(com.google.cloud.spanner.Restore)</method>
553+
</difference>
554+
<difference>
555+
<differenceType>7012</differenceType>
556+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
557+
<method>com.google.cloud.spanner.Database$Builder newDatabaseBuilder(com.google.cloud.spanner.DatabaseId)</method>
558+
</difference>
559+
<difference>
560+
<differenceType>7012</differenceType>
561+
<className>com/google/cloud/spanner/DatabaseAdminClient</className>
562+
<method>com.google.cloud.spanner.Restore$Builder newRestoreBuilder(com.google.cloud.spanner.BackupId, com.google.cloud.spanner.DatabaseId)</method>
563+
</difference>
564+
<difference>
565+
<differenceType>7013</differenceType>
566+
<className>com/google/cloud/spanner/DatabaseInfo$Builder</className>
567+
<method>com.google.cloud.spanner.DatabaseInfo$Builder setEncryptionConfig(com.google.cloud.spanner.encryption.CustomerManagedEncryption)</method>
568+
</difference>
569+
<difference>
570+
<differenceType>7013</differenceType>
571+
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
572+
<method>com.google.cloud.spanner.BackupInfo$Builder setEncryptionConfig(com.google.cloud.spanner.encryption.BackupEncryptionConfig)</method>
573+
</difference>
507574
</differences>

google-cloud-spanner/pom.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
<spanner.testenv.config.class>com.google.cloud.spanner.GceTestEnvConfig</spanner.testenv.config.class>
7474
<spanner.testenv.instance>projects/gcloud-devel/instances/spanner-testing</spanner.testenv.instance>
7575
<spanner.gce.config.project_id>gcloud-devel</spanner.gce.config.project_id>
76+
<spanner.testenv.kms_key.name>projects/gcloud-devel/locations/us-central1/keyRings/spanner-test-keyring/cryptoKeys/spanner-test-key</spanner.testenv.kms_key.name>
7677
</systemPropertyVariables>
7778
<forkedProcessTimeoutInSeconds>3000</forkedProcessTimeoutInSeconds>
7879
</configuration>
@@ -383,7 +384,7 @@
383384
</build>
384385
</profile>
385386
<profile>
386-
<!-- Profile for generating new sql test scripts. See ConnectionImplGeneratedSqlScriptTest
387+
<!-- Profile for generating new sql test scripts. See ConnectionImplGeneratedSqlScriptTest
387388
for more information. -->
388389
<id>generate-test-sql-scripts</id>
389390
<build>

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Backup.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import com.google.api.gax.paging.Page;
2424
import com.google.cloud.Policy;
2525
import com.google.cloud.Timestamp;
26+
import com.google.cloud.spanner.encryption.EncryptionInfo;
2627
import com.google.longrunning.Operation;
2728
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
2829
import com.google.spanner.admin.database.v1.RestoreDatabaseMetadata;
@@ -61,10 +62,6 @@ public Backup build() {
6162

6263
/** Creates a backup on the server based on the source of this {@link Backup} instance. */
6364
public OperationFuture<Backup, CreateBackupMetadata> create() {
64-
Preconditions.checkState(
65-
getExpireTime() != null, "Cannot create a backup without an expire time");
66-
Preconditions.checkState(
67-
getDatabase() != null, "Cannot create a backup without a source database");
6865
return dbClient.createBackup(this);
6966
}
7067

@@ -184,6 +181,7 @@ static Backup fromProto(
184181
.setExpireTime(Timestamp.fromProto(proto.getExpireTime()))
185182
.setVersionTime(Timestamp.fromProto(proto.getVersionTime()))
186183
.setDatabase(DatabaseId.of(proto.getDatabase()))
184+
.setEncryptionInfo(EncryptionInfo.fromProtoOrNull(proto.getEncryptionInfo()))
187185
.setProto(proto)
188186
.build();
189187
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/BackupInfo.java

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import com.google.api.client.util.Preconditions;
2020
import com.google.cloud.Timestamp;
21+
import com.google.cloud.spanner.encryption.BackupEncryptionConfig;
22+
import com.google.cloud.spanner.encryption.EncryptionInfo;
2123
import com.google.spanner.admin.database.v1.Database;
2224
import java.util.Objects;
2325
import javax.annotation.Nullable;
@@ -29,8 +31,29 @@ public abstract static class Builder {
2931

3032
abstract Builder setSize(long size);
3133

34+
/**
35+
* Returned when retrieving a backup.
36+
*
37+
* <p>The encryption information for the backup. If the encryption key protecting this resource
38+
* is customer managed, then kms_key_version will be filled.
39+
*/
40+
abstract Builder setEncryptionInfo(EncryptionInfo encryptionInfo);
41+
3242
abstract Builder setProto(com.google.spanner.admin.database.v1.Backup proto);
3343

44+
/**
45+
* Optional for creating a new backup.
46+
*
47+
* <p>The encryption configuration to be used for the backup. The possible configurations are
48+
* {@link com.google.cloud.spanner.encryption.CustomerManagedEncryption}, {@link
49+
* com.google.cloud.spanner.encryption.GoogleDefaultEncryption} and {@link
50+
* com.google.cloud.spanner.encryption.UseDatabaseEncryption}.
51+
*
52+
* <p>If no encryption config is given the backup will be created with the same encryption as
53+
* set by the database ({@link com.google.cloud.spanner.encryption.UseDatabaseEncryption}).
54+
*/
55+
public abstract Builder setEncryptionConfig(BackupEncryptionConfig encryptionConfig);
56+
3457
/**
3558
* Required for creating a new backup.
3659
*
@@ -70,6 +93,8 @@ abstract static class BuilderImpl extends Builder {
7093
private Timestamp versionTime;
7194
private DatabaseId database;
7295
private long size;
96+
private BackupEncryptionConfig encryptionConfig;
97+
private EncryptionInfo encryptionInfo;
7398
private com.google.spanner.admin.database.v1.Backup proto;
7499

75100
BuilderImpl(BackupId id) {
@@ -83,6 +108,8 @@ abstract static class BuilderImpl extends Builder {
83108
this.versionTime = other.versionTime;
84109
this.database = other.database;
85110
this.size = other.size;
111+
this.encryptionConfig = other.encryptionConfig;
112+
this.encryptionInfo = other.encryptionInfo;
86113
this.proto = other.proto;
87114
}
88115

@@ -113,12 +140,24 @@ public Builder setDatabase(DatabaseId database) {
113140
return this;
114141
}
115142

143+
@Override
144+
public Builder setEncryptionConfig(BackupEncryptionConfig encryptionConfig) {
145+
this.encryptionConfig = encryptionConfig;
146+
return this;
147+
}
148+
116149
@Override
117150
Builder setSize(long size) {
118151
this.size = size;
119152
return this;
120153
}
121154

155+
@Override
156+
Builder setEncryptionInfo(EncryptionInfo encryptionInfo) {
157+
this.encryptionInfo = encryptionInfo;
158+
return this;
159+
}
160+
122161
@Override
123162
Builder setProto(@Nullable com.google.spanner.admin.database.v1.Backup proto) {
124163
this.proto = proto;
@@ -142,12 +181,16 @@ public enum State {
142181
private final Timestamp versionTime;
143182
private final DatabaseId database;
144183
private final long size;
184+
private final BackupEncryptionConfig encryptionConfig;
185+
private final EncryptionInfo encryptionInfo;
145186
private final com.google.spanner.admin.database.v1.Backup proto;
146187

147188
BackupInfo(BuilderImpl builder) {
148189
this.id = builder.id;
149190
this.state = builder.state;
150191
this.size = builder.size;
192+
this.encryptionConfig = builder.encryptionConfig;
193+
this.encryptionInfo = builder.encryptionInfo;
151194
this.expireTime = builder.expireTime;
152195
this.versionTime = builder.versionTime;
153196
this.database = builder.database;
@@ -174,6 +217,22 @@ public long getSize() {
174217
return size;
175218
}
176219

220+
/**
221+
* Returns the {@link BackupEncryptionConfig} to encrypt the backup during its creation. Returns
222+
* <code>null</code> if no customer-managed encryption key should be used.
223+
*/
224+
public BackupEncryptionConfig getEncryptionConfig() {
225+
return encryptionConfig;
226+
}
227+
228+
/**
229+
* Returns the {@link EncryptionInfo} of the backup if the backup is encrypted, or <code>null
230+
* </code> if this backup is not encrypted.
231+
*/
232+
public EncryptionInfo getEncryptionInfo() {
233+
return encryptionInfo;
234+
}
235+
177236
/** Returns the expire time of the backup. */
178237
public Timestamp getExpireTime() {
179238
return expireTime;
@@ -206,20 +265,30 @@ public boolean equals(Object o) {
206265
return id.equals(that.id)
207266
&& state == that.state
208267
&& size == that.size
268+
&& Objects.equals(encryptionConfig, that.encryptionConfig)
269+
&& Objects.equals(encryptionInfo, that.encryptionInfo)
209270
&& Objects.equals(expireTime, that.expireTime)
210271
&& Objects.equals(versionTime, that.versionTime)
211272
&& Objects.equals(database, that.database);
212273
}
213274

214275
@Override
215276
public int hashCode() {
216-
return Objects.hash(id, state, size, expireTime, versionTime, database);
277+
return Objects.hash(
278+
id, state, size, encryptionConfig, encryptionInfo, expireTime, versionTime, database);
217279
}
218280

219281
@Override
220282
public String toString() {
221283
return String.format(
222-
"Backup[%s, %s, %d, %s, %s, %s]",
223-
id.getName(), state, size, expireTime, versionTime, database);
284+
"Backup[%s, %s, %d, %s, %s, %s, %s, %s]",
285+
id.getName(),
286+
state,
287+
size,
288+
encryptionConfig,
289+
encryptionInfo,
290+
expireTime,
291+
versionTime,
292+
database);
224293
}
225294
}

google-cloud-spanner/src/main/java/com/google/cloud/spanner/Database.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import com.google.api.gax.paging.Page;
2323
import com.google.cloud.Policy;
2424
import com.google.cloud.Timestamp;
25+
import com.google.cloud.spanner.encryption.CustomerManagedEncryption;
2526
import com.google.common.base.Preconditions;
2627
import com.google.longrunning.Operation;
2728
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
@@ -185,6 +186,7 @@ static Database fromProto(
185186
.setRestoreInfo(RestoreInfo.fromProtoOrNullIfDefaultInstance(proto.getRestoreInfo()))
186187
.setVersionRetentionPeriod(proto.getVersionRetentionPeriod())
187188
.setEarliestVersionTime(Timestamp.fromProto(proto.getEarliestVersionTime()))
189+
.setEncryptionConfig(CustomerManagedEncryption.fromProtoOrNull(proto.getEncryptionConfig()))
188190
.setProto(proto)
189191
.build();
190192
}

0 commit comments

Comments
 (0)