Skip to content

Commit

Permalink
samples: adds samples for CMEK (#989)
Browse files Browse the repository at this point in the history
* samples: adds samples for CMEK

Adds samples to create an encrypted database, to create an encrypted
backup and to restore to an encrypted database.

* samples: fix checkstyle violations

* samples: addresses PR comments.

* samples: fixes encryption key tests

* samples: prints user provided key in backup sample

Prints out the user provided key in the encrypted backup sample, instead
of printing out the Backup.encryption_info.kms_key_version. This should
align with the key that we are printing on the other samples (instead of
printing a key version).

* tests: verifies the key returned in create backup

Verifies that the key used in the create backup is returned in the
response correctly.

* samples: addresses PR comments
  • Loading branch information
thiagotnunes committed Mar 25, 2021
1 parent 22412e0 commit 9132c21
Show file tree
Hide file tree
Showing 10 changed files with 531 additions and 11 deletions.
Expand Up @@ -245,7 +245,7 @@ public void testBackups() throws InterruptedException, ExecutionException {
.build()));

// Verifies that the database encryption has been properly set
testDatabaseEncryption(db1);
testDatabaseEncryption(db1, keyName);

// Create two backups in parallel.
String backupId1 = testHelper.getUniqueBackupId() + "_bck1";
Expand Down Expand Up @@ -314,7 +314,7 @@ public void testBackups() throws InterruptedException, ExecutionException {
// Verifies that backup version time is the specified one
testBackupVersionTime(backup1, versionTime);
// Verifies that backup encryption has been properly set
testBackupEncryption(backup1);
testBackupEncryption(backup1, keyName);

// Insert some more data into db2 to get a timestamp from the server.
Timestamp commitTs =
Expand Down Expand Up @@ -374,7 +374,7 @@ public void testBackups() throws InterruptedException, ExecutionException {
testGetBackup(db2, backupId2, expireTime);
testUpdateBackup(backup1);
testCreateInvalidExpirationDate(db1);
testRestore(backup1, op1, versionTime);
testRestore(backup1, versionTime, keyName);

testDelete(backupId2);
testCancelBackupOperation(db1);
Expand Down Expand Up @@ -447,17 +447,17 @@ private void testBackupVersionTime(Backup backup, Timestamp versionTime) {
logger.info("Done verifying backup version time for " + backup.getId());
}

private void testDatabaseEncryption(Database database) {
private void testDatabaseEncryption(Database database, String expectedKey) {
logger.info("Verifying database encryption for " + database.getId());
assertThat(database.getEncryptionConfig()).isNotNull();
assertThat(database.getEncryptionConfig().getKmsKeyName()).isEqualTo(keyName);
assertThat(database.getEncryptionConfig().getKmsKeyName()).isEqualTo(expectedKey);
logger.info("Done verifying database encryption for " + database.getId());
}

private void testBackupEncryption(Backup backup) {
private void testBackupEncryption(Backup backup, String expectedKey) {
logger.info("Verifying backup encryption for " + backup.getId());
assertThat(backup.getEncryptionInfo()).isNotNull();
assertThat(backup.getEncryptionInfo().getKmsKeyVersion()).isNotNull();
assertThat(backup.getEncryptionInfo().getKmsKeyVersion()).contains(expectedKey);
logger.info("Done verifying backup encryption for " + backup.getId());
}

Expand Down Expand Up @@ -620,8 +620,7 @@ private void testDelete(String backupId) throws InterruptedException {
logger.info("Finished delete tests");
}

private void testRestore(
Backup backup, OperationFuture<Backup, CreateBackupMetadata> backupOp, Timestamp versionTime)
private void testRestore(Backup backup, Timestamp versionTime, String expectedKey)
throws InterruptedException, ExecutionException {
// Restore the backup to a new database.
String restoredDb = testHelper.getUniqueDatabaseId();
Expand All @@ -636,7 +635,7 @@ private void testRestore(
final Restore restore =
dbAdminClient
.newRestoreBuilder(backup.getId(), DatabaseId.of(projectId, instanceId, restoredDb))
.setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(keyName))
.setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(expectedKey))
.build();
restoreOperation = dbAdminClient.restoreDatabase(restore);
restoreOperationName = restoreOperation.getName();
Expand Down Expand Up @@ -687,7 +686,7 @@ private void testRestore(
Timestamp.fromProto(
reloadedDatabase.getProto().getRestoreInfo().getBackupInfo().getVersionTime()))
.isEqualTo(versionTime);
testDatabaseEncryption(reloadedDatabase);
testDatabaseEncryption(reloadedDatabase, expectedKey);

// Restoring the backup to an existing database should fail.
try {
Expand Down
3 changes: 3 additions & 0 deletions samples/install-without-bom/pom.xml
Expand Up @@ -143,6 +143,9 @@
<configuration>
<systemPropertyVariables>
<spanner.test.instance>spanner-testing</spanner.test.instance>
<spanner.test.key.location>us-central1</spanner.test.key.location>
<spanner.test.key.ring>spanner-test-keyring</spanner.test.key.ring>
<spanner.test.key.name>spanner-test-key</spanner.test.key.name>
<spanner.sample.database>mysample</spanner.sample.database>
<spanner.quickstart.database>quick-db</spanner.quickstart.database>
</systemPropertyVariables>
Expand Down
3 changes: 3 additions & 0 deletions samples/snapshot/pom.xml
Expand Up @@ -142,6 +142,9 @@
<configuration>
<systemPropertyVariables>
<spanner.test.instance>spanner-testing</spanner.test.instance>
<spanner.test.key.location>us-central1</spanner.test.key.location>
<spanner.test.key.ring>spanner-test-keyring</spanner.test.key.ring>
<spanner.test.key.name>spanner-test-key</spanner.test.key.name>
<spanner.sample.database>mysample</spanner.sample.database>
<spanner.quickstart.database>quick-db</spanner.quickstart.database>
</systemPropertyVariables>
Expand Down
3 changes: 3 additions & 0 deletions samples/snippets/pom.xml
Expand Up @@ -147,6 +147,9 @@
<configuration>
<systemPropertyVariables>
<spanner.test.instance>spanner-testing</spanner.test.instance>
<spanner.test.key.location>us-central1</spanner.test.key.location>
<spanner.test.key.ring>spanner-test-keyring</spanner.test.key.ring>
<spanner.test.key.name>spanner-test-key</spanner.test.key.name>
<spanner.sample.database>mysample</spanner.sample.database>
<spanner.quickstart.database>quick-db</spanner.quickstart.database>
</systemPropertyVariables>
Expand Down
@@ -0,0 +1,107 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.spanner;

// [START spanner_create_backup_with_encryption_key]

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.Timestamp;
import com.google.cloud.spanner.Backup;
import com.google.cloud.spanner.BackupId;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.encryption.EncryptionConfigs;
import com.google.spanner.admin.database.v1.CreateBackupMetadata;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.threeten.bp.LocalDateTime;
import org.threeten.bp.OffsetDateTime;

public class CreateBackupWithEncryptionKey {

static void createBackupWithEncryptionKey() throws InterruptedException {
// TODO(developer): Replace these variables before running the sample.
String projectId = "my-project";
String instanceId = "my-instance";
String databaseId = "my-database";
String backupId = "my-backup";
String kmsKeyName =
"projects/" + projectId + "/locations/<location>/keyRings/<keyRing>/cryptoKeys/<keyId>";

try (Spanner spanner =
SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient();
createBackupWithEncryptionKey(
adminClient,
projectId,
instanceId,
databaseId,
backupId,
kmsKeyName);
}
}

static Void createBackupWithEncryptionKey(DatabaseAdminClient adminClient,
String projectId, String instanceId, String databaseId, String backupId, String kmsKeyName)
throws InterruptedException {
// Set expire time to 14 days from now.
final Timestamp expireTime = Timestamp.ofTimeMicroseconds(TimeUnit.MICROSECONDS.convert(
System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14), TimeUnit.MILLISECONDS));
final Backup backupToCreate = adminClient
.newBackupBuilder(BackupId.of(projectId, instanceId, backupId))
.setDatabase(DatabaseId.of(projectId, instanceId, databaseId))
.setExpireTime(expireTime)
.setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName))
.build();
final OperationFuture<Backup, CreateBackupMetadata> operation = adminClient
.createBackup(backupToCreate);

Backup backup;
try {
System.out.println("Waiting for operation to complete...");
backup = operation.get(1200, TimeUnit.SECONDS);
} catch (ExecutionException e) {
// If the operation failed during execution, expose the cause.
throw SpannerExceptionFactory.asSpannerException(e.getCause());
} catch (InterruptedException e) {
// Throw when a thread is waiting, sleeping, or otherwise occupied,
// and the thread is interrupted, either before or during the activity.
throw SpannerExceptionFactory.propagateInterrupt(e);
} catch (TimeoutException e) {
// If the operation timed out propagates the timeout
throw SpannerExceptionFactory.propagateTimeout(e);
}

System.out.printf(
"Backup %s of size %d bytes was created at %s using encryption key %s%n",
backup.getId().getName(),
backup.getSize(),
LocalDateTime.ofEpochSecond(
backup.getProto().getCreateTime().getSeconds(),
backup.getProto().getCreateTime().getNanos(),
OffsetDateTime.now().getOffset()),
kmsKeyName
);

return null;
}
}
// [END spanner_create_backup_with_encryption_key]
@@ -0,0 +1,101 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.spanner;

// [START spanner_create_database_with_encryption_key]

import com.google.api.gax.longrunning.OperationFuture;
import com.google.cloud.spanner.Database;
import com.google.cloud.spanner.DatabaseAdminClient;
import com.google.cloud.spanner.DatabaseId;
import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.encryption.EncryptionConfigs;
import com.google.spanner.admin.database.v1.CreateDatabaseMetadata;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CreateDatabaseWithEncryptionKey {

static void createDatabaseWithEncryptionKey() {
// TODO(developer): Replace these variables before running the sample.
String projectId = "my-project";
String instanceId = "my-instance";
String databaseId = "my-database";
String kmsKeyName =
"projects/" + projectId + "/locations/<location>/keyRings/<keyRing>/cryptoKeys/<keyId>";

try (Spanner spanner =
SpannerOptions.newBuilder().setProjectId(projectId).build().getService()) {
DatabaseAdminClient adminClient = spanner.getDatabaseAdminClient();
createDatabaseWithEncryptionKey(
adminClient,
projectId,
instanceId,
databaseId,
kmsKeyName);
}
}

static Void createDatabaseWithEncryptionKey(DatabaseAdminClient adminClient,
String projectId, String instanceId, String databaseId, String kmsKeyName) {
final Database databaseToCreate = adminClient
.newDatabaseBuilder(DatabaseId.of(projectId, instanceId, databaseId))
.setEncryptionConfig(EncryptionConfigs.customerManagedEncryption(kmsKeyName))
.build();
final OperationFuture<Database, CreateDatabaseMetadata> operation = adminClient
.createDatabase(databaseToCreate, Arrays.asList(
"CREATE TABLE Singers ("
+ " SingerId INT64 NOT NULL,"
+ " FirstName STRING(1024),"
+ " LastName STRING(1024),"
+ " SingerInfo BYTES(MAX)"
+ ") PRIMARY KEY (SingerId)",
"CREATE TABLE Albums ("
+ " SingerId INT64 NOT NULL,"
+ " AlbumId INT64 NOT NULL,"
+ " AlbumTitle STRING(MAX)"
+ ") PRIMARY KEY (SingerId, AlbumId),"
+ " INTERLEAVE IN PARENT Singers ON DELETE CASCADE"
));
try {
System.out.println("Waiting for operation to complete...");
Database createdDatabase = operation.get(120, TimeUnit.SECONDS);

System.out.printf(
"Database %s created with encryption key %s%n",
createdDatabase.getId(),
createdDatabase.getEncryptionConfig().getKmsKeyName()
);
} catch (ExecutionException e) {
// If the operation failed during execution, expose the cause.
throw SpannerExceptionFactory.asSpannerException(e.getCause());
} catch (InterruptedException e) {
// Throw when a thread is waiting, sleeping, or otherwise occupied,
// and the thread is interrupted, either before or during the activity.
throw SpannerExceptionFactory.propagateInterrupt(e);
} catch (TimeoutException e) {
// If the operation timed out propagates the timeout
throw SpannerExceptionFactory.propagateTimeout(e);
}
return null;
}
}
// [END spanner_create_database_with_encryption_key]

0 comments on commit 9132c21

Please sign in to comment.