本页面介绍了如何使用手动创建的客户管理的加密密钥 (CMEK)。
如需详细了解 CMEK,请参阅客户管理的加密密钥 (CMEK)。
创建支持 CMEK 的数据库
在 Cloud Key Management Service (Cloud KMS) 中创建一个密钥。Spanner 支持:
密钥必须与 Spanner 实例位于同一位置。 例如,如果您的 Spanner 实例配置为
us-west1
, 那么您的密钥环位置也必须为us-west1
。并非每个 Spanner 多区域实例配置 在 Cloud KMS。您将无法创建启用 CMEK 的数据库 这些 Spanner 实例中的资源。
如果您已在正确的位置具有 Cloud KMS 密钥,则可以跳过此步骤。
如需了解详情,请参阅以下资源:
向 Spanner 授予对密钥的访问权限。
在 Cloud Shell 中,创建并显示服务代理,或者显示该服务代理 如果账号已存在:
gcloud beta services identity create --service=spanner.googleapis.com \ --project=my_spanner_project
如果系统提示您安装 gcloud Beta 命令组件,请输入
Y
。安装后,该命令会自动重启。通过 gcloud services identity 命令会创建或获取 Service Agent Spanner 可以使用 Cloud KMS 密钥, 。
服务账号 ID 的格式与电子邮件地址类似:
Service identity created: service-xxx@gcp-sa-spanner.iam.gserviceaccount.com
将
cloudkms.cryptoKeyEncrypterDecrypter
角色授予服务账号:gcloud kms keys add-iam-policy-binding my_kms_key \ --location my_kms_key_location \ --keyring my_kms_key_ring \ --project=my_kms_project \ --member serviceAccount:service-xxx@gcp-sa-spanner.iam.gserviceaccount.com \ --role roles/cloudkms.cryptoKeyEncrypterDecrypter
您将看到:
Updated IAM policy for key [my-kms-key]
此角色可确保服务账号有权使用 Cloud KMS 密钥进行加密和解密。如需了解详情,请参阅 Cloud KMS 权限和角色。
创建数据库并指定您的 Cloud KMS 密钥。
控制台
1. 前往 Google Cloud 控制台中的 Spanner 实例页面。
2. 点击要在其中创建数据库的实例名称。
3.点击创建数据库并填写必填字段。
4.点击显示加密选项。
5. 选择使用客户管理的加密密钥 (CMEK)。
6.从下拉列表中选择您的密钥。
密钥列表仅限于当前 Google Cloud 项目。如需使用其他 Google Cloud 项目中的密钥,请创建 使用 gcloud 而不是 Google Cloud 控制台管理数据库。
创建数据库后,您可以通过查看数据库详细信息页面来验证该数据库是否为启用 CMEK 的数据库。
gcloud
gcloud spanner databases create example_db \ --project=my_spanner_project \ --instance=my_spanner_instance \ --ddl="CREATE TABLE Users (Id INT64 NOT NULL, FirstName STRING(100) NOT NULL, LastName STRING(100) NOT NULL,) PRIMARY KEY (Id)" \ --kms-project=my_kms_project \ --kms-location=my_kms_key_location \ --kms-keyring=my_kms_key_ring \ --kms-key=my_kms_key
如需验证数据库是否为启用 CMEK 的数据库,请执行以下操作:
gcloud spanner databases describe example_db \ --project=my_spanner_project \ --instance=my_spanner_instance
启用 CMEK 的数据库包含一个
encryptionConfig
字段,如以下示例所示:encryptionConfig: kmsKeyName:projects/my-kms-project/locations/eur5/keyRings/my-kms-key-ring/cryptoKeys/my-kms-key name: projects/my-spanner-project/instances/my-instance/databases/my-db state: READY
C#
using Google.Cloud.Spanner.Admin.Database.V1; using Google.Cloud.Spanner.Common.V1; using System; using System.Threading.Tasks; public class CreateDatabaseWithEncryptionKeyAsyncSample { public async Task<Database> CreateDatabaseWithEncryptionKeyAsync(string projectId, string instanceId, string databaseId, CryptoKeyName kmsKeyName) { // Create a DatabaseAdminClient instance that can be used to execute a // CreateDatabaseRequest with custom encryption configuration options. DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create(); // Define create table statement for table #1. var createSingersTable = @"CREATE TABLE Singers ( SingerId INT64 NOT NULL, FirstName STRING(1024), LastName STRING(1024), ComposerInfo BYTES(MAX) ) PRIMARY KEY (SingerId)"; // Define create table statement for table #2. var createAlbumsTable = @"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"; // Create the CreateDatabase request with encryption configuration and execute it. var request = new CreateDatabaseRequest { ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId), CreateStatement = $"CREATE DATABASE `{databaseId}`", ExtraStatements = { createSingersTable, createAlbumsTable }, EncryptionConfig = new EncryptionConfig { KmsKeyNameAsCryptoKeyName = kmsKeyName, }, }; var operation = await databaseAdminClient.CreateDatabaseAsync(request); // Wait until the operation has finished. Console.WriteLine("Waiting for the operation to finish."); var completedResponse = await operation.PollUntilCompletedAsync(); if (completedResponse.IsFaulted) { Console.WriteLine($"Error while creating database: {completedResponse.Exception}"); throw completedResponse.Exception; } var database = completedResponse.Result; Console.WriteLine($"Database {database.Name} created with encryption key {database.EncryptionConfig.KmsKeyName}"); return database; } }
C++
Go
Java
Node.js
PHP
Python
Ruby
# project_id = "Your Google Cloud project ID" # instance_id = "Your Spanner instance ID" # database_id = "Your Spanner database ID" # kms_key_name = "Database eencryption KMS key" require "google/cloud/spanner" spanner = Google::Cloud::Spanner.new project: project_id instance = spanner.instance instance_id job = instance.create_database database_id, statements: [ "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" ], encryption_config: { kms_key_name: kms_key_name } puts "Waiting for create database operation to complete" job.wait_until_done! database = job.database puts "Database #{database.database_id} created with encryption key #{database.encryption_config.kms_key_name}"
查看正在使用的密钥版本
密钥版本的相关信息来自数据库的 encryption_info
字段。
当数据库的密钥版本发生更改时,更改不会立即传播到 encryption_info
。更改可能会延迟一段时间才会在信息字段中反映。
控制台
加密信息显示在数据库详细信息页面上。
gcloud
通过调用 databases describe
或 databases list
获取数据库的 encryption_info
。例如:
gcloud spanner databases describe example_db \
--project=my_spanner_project \
--instance=my_spanner_instance
输出如下:
name: projects/my-project/instances/test-instance/databases/example-db
encryptionConfig:
kmsKeyName: projects/google.com:cloud-spanner-demo/locations/us-central1/keyRings/cmek_demo/cryptoKeys/backup-key
encryptionInfo:
encryptionType: CUSTOMER_MANAGED_ENCRYPTION
停用密钥
按照每个密钥版本的下列说明,停用正在使用的密钥版本。
等待更改生效。停用密钥可能需要长达 3 小时来完成传播。
确认数据是否不再能够访问:
gcloud spanner databases execute-sql example_db \ --project=my_spanner_project \ --instance=my_spanner_instance \ --sql='SELECT * FROM Users'
您将看到以下错误:
KMS key required by the Spanner resource is not accessible.
启用密钥
按照每个密钥版本的说明,启用数据库正在使用的密钥版本。
等待更改生效。启用密钥可能需要长达 3 小时才能完成传播。
确认数据是否可访问:
gcloud spanner databases execute-sql example_db \ --project=my_spanner_project \ --instance=my_spanner_instance \ --sql='SELECT * FROM Users'
如果更改已生效,则该命令会成功执行。
备份数据库
默认情况下,从数据库创建的备份使用与数据库本身相同的加密配置。(可选)可为备份指定不同的加密配置。
如需创建备份,请执行以下操作:
控制台
转到 Google Cloud 控制台中的数据库详细信息页面。
在备份/恢复标签页中,点击创建。
输入备份名称,然后选择失效日期。
选择使用客户管理的加密密钥 (CMEK),然后从下拉列表中选择一个密钥。
点击创建。
备份表会显示每个备份的加密信息。
gcloud
gcloud spanner backups create my_backup \
--project=my_spanner_project \
--instance=my_spanner_instance \
--database=example_db \
--retention-period=1y \
--encryption-type=customer_managed_encryption \
--kms-project=my_kms_project \
--kms-location=my_kms_key_location \
--kms-keyring=my_kms_key_ring \
--kms-key=my_kms_key
--async
如需验证创建的备份是否采用了 CMEK 加密,请执行以下操作:
gcloud spanner backups describe my_backup \
--project=my_spanner_project \
--instance=my_spanner_instance
C#
using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using Google.Protobuf.WellKnownTypes;
using System;
using System.Threading.Tasks;
public class CreateBackupWithEncryptionKeyAsyncSample
{
public async Task<Backup> CreateBackupWithEncryptionKeyAsync(string projectId, string instanceId, string databaseId, string backupId, CryptoKeyName kmsKeyName)
{
// Create a DatabaseAdminClient instance.
DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create();
// Create the CreateBackupRequest with encryption configuration.
CreateBackupRequest request = new CreateBackupRequest
{
ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId),
BackupId = backupId,
Backup = new Backup
{
DatabaseAsDatabaseName = DatabaseName.FromProjectInstanceDatabase(projectId, instanceId, databaseId),
ExpireTime = DateTime.UtcNow.AddDays(14).ToTimestamp(),
},
EncryptionConfig = new CreateBackupEncryptionConfig
{
EncryptionType = CreateBackupEncryptionConfig.Types.EncryptionType.CustomerManagedEncryption,
KmsKeyNameAsCryptoKeyName = kmsKeyName,
},
};
// Execute the CreateBackup request.
var operation = await databaseAdminClient.CreateBackupAsync(request);
Console.WriteLine("Waiting for the operation to finish.");
// Poll until the returned long-running operation is complete.
var completedResponse = await operation.PollUntilCompletedAsync();
if (completedResponse.IsFaulted)
{
Console.WriteLine($"Error while creating backup: {completedResponse.Exception}");
throw completedResponse.Exception;
}
var backup = completedResponse.Result;
Console.WriteLine($"Backup {backup.Name} of size {backup.SizeBytes} bytes " +
$"was created at {backup.CreateTime} " +
$"using encryption key {kmsKeyName}");
return backup;
}
}
C++
Go
Java
Node.js
PHP
Python
Ruby
# project_id = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID"
# backup_id = "Your Spanner backup ID"
# kms_key_name = "Your backup encryption database KMS key"
require "google/cloud/spanner"
spanner = Google::Cloud::Spanner.new project: project_id
client = spanner.client instance_id, database_id
instance = spanner.instance instance_id
database = instance.database database_id
expire_time = Time.now + 14 * 24 * 3600 # 14 days from now
encryption_config = {
encryption_type: :CUSTOMER_MANAGED_ENCRYPTION,
kms_key_name: kms_key_name
}
job = database.create_backup backup_id, expire_time, version_time: version_time, encryption_config: encryption_config
puts "Backup operation in progress"
job.wait_until_done!
backup = instance.backup backup_id
puts "Backup #{backup.backup_id} of size #{backup.size_in_bytes} bytes was created at #{backup.create_time} using encryption key #{kms_key_name}"
使用备份进行恢复
默认情况下,从备份恢复的数据库使用与备份本身相同的加密配置,但您可以通过为恢复的数据库指定不同的加密配置来替换此行为。如果备份受 CMEK 保护,则用于创建备份的密钥版本必须可用才能进行解密。
要恢复数据库,请使用:
控制台
前往 Cloud 控制台中的实例详情页面。
在备份/恢复标签页中,选择一个备份并点击恢复。
选择要恢复的实例并命名所恢复的数据库。
如果您希望将 CMEK 用于所恢复的数据库,请选择使用客户管理的加密密钥 (CMEK),然后从下拉列表中选择一个密钥。
gcloud
gcloud spanner databases restore --async \
--project=my_spanner_project \
--destination-instance=destination_instance \
--destination-database=example_db_restored \
--source-instance=my_spanner_instance \
--source-backup=my_backup
验证恢复的数据库是否采用 CMEK 加密:
gcloud spanner databases describe example_db_restored \
--project=my_spanner_project \
--instance=destination_instance
如需了解详情,请参阅从备份恢复数据库。
C#
using Google.Cloud.Spanner.Admin.Database.V1;
using Google.Cloud.Spanner.Common.V1;
using System;
using System.Threading.Tasks;
public class RestoreDatabaseWithEncryptionAsyncSample
{
public async Task<Database> RestoreDatabaseWithEncryptionAsync(string projectId, string instanceId, string databaseId, string backupId, CryptoKeyName kmsKeyName)
{
// Create a DatabaseAdminClient instance.
DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.Create();
// Create the RestoreDatabaseRequest with encryption configuration.
RestoreDatabaseRequest request = new RestoreDatabaseRequest
{
ParentAsInstanceName = InstanceName.FromProjectInstance(projectId, instanceId),
DatabaseId = databaseId,
BackupAsBackupName = BackupName.FromProjectInstanceBackup(projectId, instanceId, backupId),
EncryptionConfig = new RestoreDatabaseEncryptionConfig
{
EncryptionType = RestoreDatabaseEncryptionConfig.Types.EncryptionType.CustomerManagedEncryption,
KmsKeyNameAsCryptoKeyName = kmsKeyName,
}
};
// Execute the RestoreDatabase request.
var operation = await databaseAdminClient.RestoreDatabaseAsync(request);
Console.WriteLine("Waiting for the operation to finish.");
// Poll until the returned long-running operation is complete.
var completedResponse = await operation.PollUntilCompletedAsync();
if (completedResponse.IsFaulted)
{
Console.WriteLine($"Error while restoring database: {completedResponse.Exception}");
throw completedResponse.Exception;
}
var database = completedResponse.Result;
var restoreInfo = database.RestoreInfo;
Console.WriteLine($"Database {restoreInfo.BackupInfo.SourceDatabase} " +
$"restored to {database.Name} " +
$"from backup {restoreInfo.BackupInfo.Backup} " +
$"using encryption key {database.EncryptionConfig.KmsKeyName}");
return database;
}
}
C++
Go
Java
Node.js
PHP
Python
Ruby
# project_id = "Your Google Cloud project ID"
# instance_id = "Your Spanner instance ID"
# database_id = "Your Spanner database ID of where to restore"
# backup_id = "Your Spanner backup ID"
# kms_key_name = "Your backup encryption database KMS key"
require "google/cloud/spanner"
spanner = Google::Cloud::Spanner.new project: project_id
instance = spanner.instance instance_id
backup = instance.backup backup_id
encryption_config = {
encryption_type: :CUSTOMER_MANAGED_ENCRYPTION,
kms_key_name: kms_key_name
}
job = backup.restore database_id, encryption_config: encryption_config
puts "Waiting for restore backup operation to complete"
job.wait_until_done!
database = job.database
restore_info = database.restore_info
puts "Database #{restore_info.backup_info.source_database_id} was restored to #{database.database_id} from backup #{restore_info.backup_info.backup_id} using encryption key #{database.encryption_config.kms_key_name}"
查看 Cloud KMS 密钥的审核日志
确保针对您的项目中的 Cloud KMS API 启用日志记录。
转到 Cloud Console 中的日志浏览器。
通过将以下行添加到查询构建器,将日志条目限制为 Cloud KMS 密钥:
resource.type="cloudkms_cryptokey" resource.labels.location="my_kms_key_location" resource.labels.key_ring_id="my_kms_key_ring" resource.labels.crypto_key_id="my_kms_key"
在正常操作下,系统会使用
INFO
严重程度记录加密和解密操作。这些条目会记录为 Spanner 中的可用区 大约每 5 分钟对 Cloud KMS 密钥进行一次轮询。如果 Spanner 无法访问密钥,操作将被记录为
ERROR
。