顧客管理の暗号鍵(CMEK)でデータベースを保護する

このページでは、Spanner 用に顧客管理の暗号鍵(CMEK)を使用する方法について説明します。

CMEK の詳細については、顧客管理の暗号鍵(CMEK)をご覧ください。

CMEK 対応データベースを作成する

  1. Cloud Key Management Service(Cloud KMS)で鍵を作成します。Spanner は、次の対象をサポートしています。

    鍵は Spanner インスタンスと同じロケーションにあることが必要です。たとえば、Spanner インスタンス構成が us-west1 の場合は、キーリングのロケーションも us-west1 でなければなりません。

    すべての Spanner マルチリージョン インスタンス構成で、Cloud KMS に対応するキーリング ロケーションが存在するわけではありません。そうした Spanner インスタンスでは、CMEK 対応データベースを作成できません。

    すでに適切な場所に Cloud KMS 鍵を配置している場合は、このステップをスキップできます。

    詳しくは、次のリソースをご覧ください。

  2. Spanner に鍵へのアクセス権を付与します。

    1. Cloud Shell で、Google が管理するサービス アカウントを作成して表示します。
    2. Cloud Shell で、Google が管理するサービス アカウントを作成して表示します。アカウントがすでに存在する場合は表示します。

      gcloud beta services identity create --service=spanner.googleapis.com \
          --project=my-spanner-project
      

      gcloud ベータ版コマンド コンポーネントのインストールを求められたら、「Y」と入力します。インストール後、コマンドが自動的に再開されます。

      gcloud services ID コマンドは、ユーザーに代わって Spanner が Cloud KMS 鍵にアクセスする際に使用する特別な Google 管理のサービス アカウントを作成または取得します。

      サービス アカウント ID は、メールアドレスに類似した形式です。

      Service identity created: service-xxx@gcp-sa-spanner.iam.gserviceaccount.com
      
    3. サービス アカウントに 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 の権限とロールをご覧ください。

  3. データベースを作成し、Cloud KMS 鍵を指定します。

    Console

    1. Google Cloud コンソールで、Spanner の [インスタンス] ページに移動します。

    Cloud コンソールに移動

    2. データベースを作成するインスタンス名をクリックします。

    3. [データベースを作成] をクリックし、必須のフィールドを入力します。

    4. [暗号化オプションを表示する] をクリックします。

    5. [顧客管理の暗号鍵(CMEK)を使用する] を選択します。

    6 プルダウン リストから鍵を選択します。

    鍵のリストは、現在の Google Cloud プロジェクトに限定されます。別の Google Cloud プロジェクトの鍵を使用するには、Google Cloud コンソールではなく gcloud を使用してデータベースを作成します。

    データベースが作成されたら、[データベースの詳細] ページを表示すると、データベースが 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++

    void CreateDatabaseWithEncryptionKey(
        google::cloud::spanner_admin::DatabaseAdminClient client,
        std::string const& project_id, std::string const& instance_id,
        std::string const& database_id,
        google::cloud::KmsKeyName const& encryption_key) {
      google::cloud::spanner::Database database(project_id, instance_id,
                                                database_id);
      google::spanner::admin::database::v1::CreateDatabaseRequest request;
      request.set_parent(database.instance().FullName());
      request.set_create_statement("CREATE DATABASE `" + database.database_id() +
                                   "`");
      request.add_extra_statements(R"""(
          CREATE TABLE Singers (
              SingerId   INT64 NOT NULL,
              FirstName  STRING(1024),
              LastName   STRING(1024),
              SingerInfo BYTES(MAX),
              FullName   STRING(2049)
                  AS (ARRAY_TO_STRING([FirstName, LastName], " ")) STORED
          ) PRIMARY KEY (SingerId))""");
      request.add_extra_statements(R"""(
          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)""");
      request.mutable_encryption_config()->set_kms_key_name(
          encryption_key.FullName());
      auto db = client.CreateDatabase(request).get();
      if (!db) throw std::move(db).status();
      std::cout << "Database " << db->name() << " created";
      std::cout << " using encryption key " << encryption_key.FullName();
      std::cout << ".\n";
    }

    Go

    import (
    	"context"
    	"fmt"
    	"io"
    	"regexp"
    
    	database "cloud.google.com/go/spanner/admin/database/apiv1"
    	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
    )
    
    func createDatabaseWithCustomerManagedEncryptionKey(ctx context.Context, w io.Writer, db, kmsKeyName string) error {
    	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
    	// kmsKeyName = `projects/<project>/locations/<location>/keyRings/<key_ring>/cryptoKeys/<kms_key_name>`
    	matches := regexp.MustCompile("^(.+)/databases/(.+)$").FindStringSubmatch(db)
    	if matches == nil || len(matches) != 3 {
    		return fmt.Errorf("createDatabaseWithCustomerManagedEncryptionKey: invalid database id %q", db)
    	}
    	instanceName := matches[1]
    	databaseId := matches[2]
    
    	adminClient, err := database.NewDatabaseAdminClient(ctx)
    	if err != nil {
    		return fmt.Errorf("createDatabaseWithCustomerManagedEncryptionKey.NewDatabaseAdminClient: %w", err)
    	}
    	defer adminClient.Close()
    
    	// Create a database with tables using a Customer Managed Encryption Key
    	req := adminpb.CreateDatabaseRequest{
    		Parent:          instanceName,
    		CreateStatement: "CREATE DATABASE `" + databaseId + "`",
    		ExtraStatements: []string{
    			`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`,
    		},
    		EncryptionConfig: &adminpb.EncryptionConfig{KmsKeyName: kmsKeyName},
    	}
    	op, err := adminClient.CreateDatabase(ctx, &req)
    	if err != nil {
    		return fmt.Errorf("createDatabaseWithCustomerManagedEncryptionKey.CreateDatabase: %w", err)
    	}
    	dbObj, err := op.Wait(ctx)
    	if err != nil {
    		return fmt.Errorf("createDatabaseWithCustomerManagedEncryptionKey.Wait: %w", err)
    	}
    	fmt.Fprintf(w, "Created database [%s] using encryption key %q\n", dbObj.Name, dbObj.EncryptionConfig.KmsKeyName)
    	return nil
    }
    

    Java

    
    import com.google.cloud.spanner.Spanner;
    import com.google.cloud.spanner.SpannerExceptionFactory;
    import com.google.cloud.spanner.SpannerOptions;
    import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
    import com.google.common.collect.ImmutableList;
    import com.google.spanner.admin.database.v1.CreateDatabaseRequest;
    import com.google.spanner.admin.database.v1.Database;
    import com.google.spanner.admin.database.v1.EncryptionConfig;
    import com.google.spanner.admin.database.v1.InstanceName;
    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.createDatabaseAdminClient()) {
          createDatabaseWithEncryptionKey(
              adminClient,
              projectId,
              instanceId,
              databaseId,
              kmsKeyName);
        }
      }
    
      static void createDatabaseWithEncryptionKey(DatabaseAdminClient adminClient,
          String projectId, String instanceId, String databaseId, String kmsKeyName) {
        InstanceName instanceName = InstanceName.of(projectId, instanceId);
        CreateDatabaseRequest request = CreateDatabaseRequest.newBuilder()
            .setParent(instanceName.toString())
            .setCreateStatement("CREATE DATABASE `" + databaseId + "`")
            .setEncryptionConfig(EncryptionConfig.newBuilder().setKmsKeyName(kmsKeyName).build())
            .addAllExtraStatements(
                ImmutableList.of(
                    "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"
                ))
            .build();
        try {
          System.out.println("Waiting for operation to complete...");
          Database createdDatabase =
              adminClient.createDatabaseAsync(request).get(120, TimeUnit.SECONDS);
    
          System.out.printf(
              "Database %s created with encryption key %s%n",
              createdDatabase.getName(),
              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);
        }
      }
    }

    Node.js

    
    // Imports the Google Cloud client library
    const {Spanner, protos} = require('@google-cloud/spanner');
    
    /**
     * TODO(developer): Uncomment the following lines before running the sample.
     */
    // const projectId = 'my-project-id';
    // const instanceId = 'my-instance';
    // const databaseId = 'my-database';
    // const keyName =
    //   'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key';
    
    // creates a client
    const spanner = new Spanner({
      projectId: projectId,
    });
    
    // Gets a reference to a Cloud Spanner Database Admin Client object
    const databaseAdminClient = spanner.getDatabaseAdminClient();
    
    // Creates a database
    const [operation] = await databaseAdminClient.createDatabase({
      createStatement: 'CREATE DATABASE `' + databaseId + '`',
      parent: databaseAdminClient.instancePath(projectId, instanceId),
      encryptionConfig:
        (protos.google.spanner.admin.database.v1.EncryptionConfig = {
          kmsKeyName: keyName,
        }),
    });
    
    console.log(`Waiting for operation on ${databaseId} to complete...`);
    await operation.promise();
    
    console.log(`Created database ${databaseId} on instance ${instanceId}.`);
    
    // Get encryption key
    const [metadata] = await databaseAdminClient.getDatabase({
      name: databaseAdminClient.databasePath(projectId, instanceId, databaseId),
    });
    
    console.log(
      `Database encrypted with key ${metadata.encryptionConfig.kmsKeyName}.`
    );

    PHP

    use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
    use Google\Cloud\Spanner\Admin\Database\V1\CreateDatabaseRequest;
    use Google\Cloud\Spanner\Admin\Database\V1\EncryptionConfig;
    
    /**
     * Creates an encrypted database with tables for sample data.
     * Example:
     * ```
     * create_database_with_encryption_key($projectId, $instanceId, $databaseId, $kmsKeyName);
     * ```
     *
     * @param string $projectId The Google Cloud project ID.
     * @param string $instanceId The Spanner instance ID.
     * @param string $databaseId The Spanner database ID.
     * @param string $kmsKeyName The KMS key used for encryption.
     */
    function create_database_with_encryption_key(
        string $projectId,
        string $instanceId,
        string $databaseId,
        string $kmsKeyName
    ): void {
        $databaseAdminClient = new DatabaseAdminClient();
        $instanceName = DatabaseAdminClient::instanceName($projectId, $instanceId);
    
        $createDatabaseRequest = new CreateDatabaseRequest();
        $createDatabaseRequest->setParent($instanceName);
        $createDatabaseRequest->setCreateStatement(sprintf('CREATE DATABASE `%s`', $databaseId));
        $createDatabaseRequest->setExtraStatements([
            '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'
        ]);
    
        if (!empty($kmsKeyName)) {
            $encryptionConfig = new EncryptionConfig();
            $encryptionConfig->setKmsKeyName($kmsKeyName);
            $createDatabaseRequest->setEncryptionConfig($encryptionConfig);
        }
    
        $operationResponse = $databaseAdminClient->createDatabase($createDatabaseRequest);
        printf('Waiting for operation to complete...' . PHP_EOL);
        $operationResponse->pollUntilComplete();
    
        if ($operationResponse->operationSucceeded()) {
            $database = $operationResponse->getResult();
            printf(
                'Created database %s on instance %s with encryption key %s' . PHP_EOL,
                $databaseId,
                $instanceId,
                $database->getEncryptionConfig()->getKmsKeyName()
            );
        } else {
            $error = $operationResponse->getError();
            printf('Failed to create encrypted database: %s' . PHP_EOL, $error->getMessage());
        }
    }

    Python

    def create_database_with_encryption_key(instance_id, database_id, kms_key_name):
        """Creates a database with tables using a Customer Managed Encryption Key (CMEK)."""
        from google.cloud.spanner_admin_database_v1 import EncryptionConfig
        from google.cloud.spanner_admin_database_v1.types import \
            spanner_database_admin
    
        spanner_client = spanner.Client()
        database_admin_api = spanner_client.database_admin_api
    
        request = spanner_database_admin.CreateDatabaseRequest(
            parent=database_admin_api.instance_path(spanner_client.project, instance_id),
            create_statement=f"CREATE DATABASE `{database_id}`",
            extra_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=EncryptionConfig(kms_key_name=kms_key_name),
        )
    
        operation = database_admin_api.create_database(request=request)
    
        print("Waiting for operation to complete...")
        database = operation.result(OPERATION_TIMEOUT_SECONDS)
    
        print(
            "Database {} created with encryption key {}".format(
                database.name, database.encryption_config.kms_key_name
            )
        )
    
    

    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 に伝播されません。変更が情報フィールドに反映されるまでの間に遅延が発生する場合があります。

Console

暗号化の情報は、[データベースの詳細] ページに表示されます。

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

鍵を無効にする

  1. 使用中の鍵バージョンは、こちらの手順に沿って無効にしてください。

  2. 変更が有効になるまで待ちます。鍵の有効化が伝播されるまでには、最長で 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. のエラーが表示されます。

鍵を有効にする

  1. 鍵バージョンごとにこちらの手順に沿って、データベースで使用される鍵バージョンを有効にします。

  2. 変更が有効になるまで待ちます。鍵の有効化が伝播されるまでには、最長で 3 時間を要する場合があります。

    データにアクセスできることを確認します。

    gcloud spanner databases execute-sql example-db \
        --project=my-spanner-project \
        --instance=my-spanner-instance \
        --sql='SELECT * FROM Users'
    

    変更が有効になっていると、コマンドは正常に実行されます。

データベースのバックアップ

デフォルトでは、データベースから作成されたバックアップは、データベース自体と同じ暗号化構成ファイルを使用します。必要に応じて、別の暗号化構成ファイルをバックアップに指定することもできます。

バックアップを作成するには:

Console

  1. Cloud Console で [データベースの詳細] ページに移動します。

    Cloud Console に移動

  2. [バックアップ / 復元] タブで、[作成] をクリックします。

  3. バックアップの名前を入力し、有効期限を選択します。

  4. [顧客管理の暗号鍵(CMEK)を使用する] を選択し、プルダウン リストから鍵を選びます。

  5. [作成] をクリックします。

[バックアップ] テーブルには、各バックアップの暗号化情報が表示されます。

gcloud

gcloud spanner backups create my-backup \
    --project=my-spanner-project \
    --instance=my-spanner-instance \
    --database=example-db  \
    --retention-period=1y --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++

void CreateBackupWithEncryptionKey(
    google::cloud::spanner_admin::DatabaseAdminClient client,
    std::string const& project_id, std::string const& instance_id,
    std::string const& database_id, std::string const& backup_id,
    google::cloud::spanner::Timestamp expire_time,
    google::cloud::spanner::Timestamp version_time,
    google::cloud::KmsKeyName const& encryption_key) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  google::spanner::admin::database::v1::CreateBackupRequest request;
  request.set_parent(database.instance().FullName());
  request.set_backup_id(backup_id);
  request.mutable_backup()->set_database(database.FullName());
  *request.mutable_backup()->mutable_expire_time() =
      expire_time.get<google::protobuf::Timestamp>().value();
  *request.mutable_backup()->mutable_version_time() =
      version_time.get<google::protobuf::Timestamp>().value();
  request.mutable_encryption_config()->set_encryption_type(
      google::spanner::admin::database::v1::CreateBackupEncryptionConfig::
          CUSTOMER_MANAGED_ENCRYPTION);
  request.mutable_encryption_config()->set_kms_key_name(
      encryption_key.FullName());
  auto backup = client.CreateBackup(request).get();
  if (!backup) throw std::move(backup).status();
  std::cout
      << "Backup " << backup->name() << " of " << backup->database()
      << " of size " << backup->size_bytes() << " bytes as of "
      << google::cloud::spanner::MakeTimestamp(backup->version_time()).value()
      << " was created at "
      << google::cloud::spanner::MakeTimestamp(backup->create_time()).value()
      << " using encryption key " << encryption_key.FullName() << ".\n";
}

Go


import (
	"context"
	"fmt"
	"io"
	"regexp"
	"time"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	pbt "github.com/golang/protobuf/ptypes/timestamp"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func createBackupWithCustomerManagedEncryptionKey(ctx context.Context, w io.Writer, db, backupID, kmsKeyName string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	// backupID = `my-backup-id`
	// kmsKeyName = `projects/<project>/locations/<location>/keyRings/<key_ring>/cryptoKeys/<kms_key_name>`
	matches := regexp.MustCompile("^(.+)/databases/(.+)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("createBackupWithCustomerManagedEncryptionKey: invalid database id %q", db)
	}
	instanceName := matches[1]

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return fmt.Errorf("createBackupWithCustomerManagedEncryptionKey.NewDatabaseAdminClient: %w", err)
	}
	defer adminClient.Close()

	expireTime := time.Now().AddDate(0, 0, 14)
	// Create a backup for a database using a Customer Managed Encryption Key
	req := adminpb.CreateBackupRequest{
		Parent:   instanceName,
		BackupId: backupID,
		Backup: &adminpb.Backup{
			Database:   db,
			ExpireTime: &pbt.Timestamp{Seconds: expireTime.Unix(), Nanos: int32(expireTime.Nanosecond())},
		},
		EncryptionConfig: &adminpb.CreateBackupEncryptionConfig{
			KmsKeyName:     kmsKeyName,
			EncryptionType: adminpb.CreateBackupEncryptionConfig_CUSTOMER_MANAGED_ENCRYPTION,
		},
	}
	op, err := adminClient.CreateBackup(ctx, &req)
	if err != nil {
		return fmt.Errorf("createBackupWithCustomerManagedEncryptionKey.CreateBackup: %w", err)
	}
	// Wait for backup operation to complete.
	backup, err := op.Wait(ctx)
	if err != nil {
		return fmt.Errorf("createBackupWithCustomerManagedEncryptionKey.Wait: %w", err)
	}

	// Get the name, create time, backup size and encryption key from the backup.
	backupCreateTime := time.Unix(backup.CreateTime.Seconds, int64(backup.CreateTime.Nanos))
	fmt.Fprintf(w,
		"Backup %s of size %d bytes was created at %s using encryption key %s\n",
		backup.Name,
		backup.SizeBytes,
		backupCreateTime.Format(time.RFC3339),
		kmsKeyName)
	return nil
}

Java


import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.protobuf.Timestamp;
import com.google.spanner.admin.database.v1.Backup;
import com.google.spanner.admin.database.v1.BackupName;
import com.google.spanner.admin.database.v1.CreateBackupEncryptionConfig;
import com.google.spanner.admin.database.v1.CreateBackupEncryptionConfig.EncryptionType;
import com.google.spanner.admin.database.v1.CreateBackupRequest;
import com.google.spanner.admin.database.v1.DatabaseName;
import com.google.spanner.admin.database.v1.InstanceName;
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() {
    // 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.createDatabaseAdminClient()) {
      createBackupWithEncryptionKey(
          adminClient,
          projectId,
          instanceId,
          databaseId,
          backupId,
          kmsKeyName);
    }
  }

  static Void createBackupWithEncryptionKey(DatabaseAdminClient adminClient,
      String projectId, String instanceId, String databaseId, String backupId, String kmsKeyName) {
    // Set expire time to 14 days from now.
    final Timestamp expireTime =
        Timestamp.newBuilder().setSeconds(TimeUnit.MILLISECONDS.toSeconds((
            System.currentTimeMillis() + TimeUnit.DAYS.toMillis(14)))).build();
    final BackupName backupName = BackupName.of(projectId, instanceId, backupId);
    Backup backup = Backup.newBuilder()
        .setName(backupName.toString())
        .setDatabase(DatabaseName.of(projectId, instanceId, databaseId).toString())
        .setExpireTime(expireTime).build();

    final CreateBackupRequest request =
        CreateBackupRequest.newBuilder()
            .setParent(InstanceName.of(projectId, instanceId).toString())
            .setBackupId(backupId)
            .setBackup(backup)
            .setEncryptionConfig(
                CreateBackupEncryptionConfig.newBuilder()
                    .setEncryptionType(EncryptionType.CUSTOMER_MANAGED_ENCRYPTION)
                    .setKmsKeyName(kmsKeyName).build()).build();
    try {
      System.out.println("Waiting for operation to complete...");
      backup = adminClient.createBackupAsync(request).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.getName(),
        backup.getSizeBytes(),
        LocalDateTime.ofEpochSecond(
            backup.getCreateTime().getSeconds(),
            backup.getCreateTime().getNanos(),
            OffsetDateTime.now().getOffset()),
        kmsKeyName
    );

    return null;
  }
}

Node.js


// Imports the Google Cloud client library
const {Spanner, protos} = require('@google-cloud/spanner');
const {PreciseDate} = require('@google-cloud/precise-date');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const backupId = 'my-backup';
// const keyName =
//   'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner Database Admin Client object
const databaseAdminClient = spanner.getDatabaseAdminClient();

// Creates a new backup of the database
try {
  console.log(
    `Creating backup of database ${databaseAdminClient.databasePath(
      projectId,
      instanceId,
      databaseId
    )}.`
  );

  // Expire backup 14 days in the future
  const expireTime = Date.now() + 1000 * 60 * 60 * 24 * 14;

  // Create a backup of the state of the database at the current time.
  const [operation] = await databaseAdminClient.createBackup({
    parent: databaseAdminClient.instancePath(projectId, instanceId),
    backupId: backupId,
    backup: (protos.google.spanner.admin.database.v1.Backup = {
      database: databaseAdminClient.databasePath(
        projectId,
        instanceId,
        databaseId
      ),
      expireTime: Spanner.timestamp(expireTime).toStruct(),
      name: databaseAdminClient.backupPath(projectId, instanceId, backupId),
    }),
    encryptionConfig: {
      encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION',
      kmsKeyName: keyName,
    },
  });

  console.log(
    `Waiting for backup ${databaseAdminClient.backupPath(
      projectId,
      instanceId,
      backupId
    )} to complete...`
  );
  await operation.promise();

  // Verify backup is ready
  const [backupInfo] = await databaseAdminClient.getBackup({
    name: databaseAdminClient.backupPath(projectId, instanceId, backupId),
  });
  if (backupInfo.state === 'READY') {
    console.log(
      `Backup ${backupInfo.name} of size ` +
        `${backupInfo.sizeBytes} bytes was created at ` +
        `${new PreciseDate(backupInfo.createTime).toISOString()} ` +
        `using encryption key ${backupInfo.encryptionInfo.kmsKeyVersion}`
    );
  } else {
    console.error('ERROR: Backup is not ready.');
  }
} catch (err) {
  console.error('ERROR:', err);
} finally {
  // Close the spanner client when finished.
  // The databaseAdminClient does not require explicit closure. The closure of the Spanner client will automatically close the databaseAdminClient.
  spanner.close();
}

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Backup;
use \Google\Cloud\Spanner\Admin\Database\V1\Backup\State;
use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupEncryptionConfig;
use Google\Cloud\Spanner\Admin\Database\V1\CreateBackupRequest;
use Google\Cloud\Spanner\Admin\Database\V1\GetBackupRequest;
use Google\Protobuf\Timestamp;

/**
 * Create an encrypted backup.
 * Example:
 * ```
 * create_backup_with_encryption_key($projectId, $instanceId, $databaseId, $backupId, $kmsKeyName);
 * ```
 *
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $backupId The Spanner backup ID.
 * @param string $kmsKeyName The KMS key used for encryption.
 */
function create_backup_with_encryption_key(
    string $projectId,
    string $instanceId,
    string $databaseId,
    string $backupId,
    string $kmsKeyName
): void {
    $databaseAdminClient = new DatabaseAdminClient();
    $instanceFullName = DatabaseAdminClient::instanceName($projectId, $instanceId);
    $databaseFullName = DatabaseAdminClient::databaseName($projectId, $instanceId, $databaseId);
    $expireTime = new Timestamp();
    $expireTime->setSeconds((new \DateTime('+14 days'))->getTimestamp());
    $request = new CreateBackupRequest([
        'parent' => $instanceFullName,
        'backup_id' => $backupId,
        'encryption_config' => new CreateBackupEncryptionConfig([
            'kms_key_name' => $kmsKeyName,
            'encryption_type' => CreateBackupEncryptionConfig\EncryptionType::CUSTOMER_MANAGED_ENCRYPTION
        ]),
        'backup' => new Backup([
            'database' => $databaseFullName,
            'expire_time' => $expireTime
        ])
    ]);

    $operation = $databaseAdminClient->createBackup($request);

    print('Waiting for operation to complete...' . PHP_EOL);
    $operation->pollUntilComplete();

    $request = new GetBackupRequest();
    $request->setName($databaseAdminClient->backupName($projectId, $instanceId, $backupId));
    $info = $databaseAdminClient->getBackup($request);
    if (State::name($info->getState()) == 'READY') {
        printf(
            'Backup %s of size %d bytes was created at %d using encryption key %s' . PHP_EOL,
            basename($info->getName()),
            $info->getSizeBytes(),
            $info->getCreateTime()->getSeconds(),
            $info->getEncryptionInfo()->getKmsKeyVersion()
        );
    } else {
        print('Backup is not ready!' . PHP_EOL);
    }
}

Python

def create_backup_with_encryption_key(
    instance_id, database_id, backup_id, kms_key_name
):
    """Creates a backup for a database using a Customer Managed Encryption Key (CMEK)."""

    from google.cloud.spanner_admin_database_v1 import \
        CreateBackupEncryptionConfig
    from google.cloud.spanner_admin_database_v1.types import \
        backup as backup_pb

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    # Create a backup
    expire_time = datetime.utcnow() + timedelta(days=14)
    encryption_config = {
        "encryption_type": CreateBackupEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
        "kms_key_name": kms_key_name,
    }
    request = backup_pb.CreateBackupRequest(
        parent=database_admin_api.instance_path(spanner_client.project, instance_id),
        backup_id=backup_id,
        backup=backup_pb.Backup(
            database=database_admin_api.database_path(
                spanner_client.project, instance_id, database_id
            ),
            expire_time=expire_time,
        ),
        encryption_config=encryption_config,
    )
    operation = database_admin_api.create_backup(request)

    # Wait for backup operation to complete.
    backup = operation.result(2100)

    # Verify that the backup is ready.
    assert backup.state == backup_pb.Backup.State.READY

    # Get the name, create time, backup size and encryption key.
    print(
        "Backup {} of size {} bytes was created at {} using encryption key {}".format(
            backup.name, backup.size_bytes, backup.create_time, kms_key_name
        )
    )

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 によって保護されている場合、復号できるようにバックアップの作成に使用した鍵バージョンが利用可能な状態にする必要があります。

データベースを復元するには、次の操作を行います。

Console

  1. Cloud Console の [インスタンスの詳細] ページに移動します。

    Cloud Console に移動

  2. [バックアップ / 復元] タブで、バックアップを選択して [復元] をクリックします。

  3. 復元するインスタンスを選択し、復元するデータベースに名前を付けます。

  4. 復元されたデータベースで 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++

void RestoreDatabaseWithEncryptionKey(
    google::cloud::spanner_admin::DatabaseAdminClient client,
    std::string const& project_id, std::string const& instance_id,
    std::string const& database_id, std::string const& backup_id,
    google::cloud::KmsKeyName const& encryption_key) {
  google::cloud::spanner::Database database(project_id, instance_id,
                                            database_id);
  google::cloud::spanner::Backup backup(database.instance(), backup_id);
  google::spanner::admin::database::v1::RestoreDatabaseRequest request;
  request.set_parent(database.instance().FullName());
  request.set_database_id(database.database_id());
  request.set_backup(backup.FullName());
  request.mutable_encryption_config()->set_encryption_type(
      google::spanner::admin::database::v1::RestoreDatabaseEncryptionConfig::
          CUSTOMER_MANAGED_ENCRYPTION);
  request.mutable_encryption_config()->set_kms_key_name(
      encryption_key.FullName());
  auto restored_db = client.RestoreDatabase(request).get();
  if (!restored_db) throw std::move(restored_db).status();
  std::cout << "Database";
  if (restored_db->restore_info().source_type() ==
      google::spanner::admin::database::v1::BACKUP) {
    auto const& backup_info = restored_db->restore_info().backup_info();
    std::cout << " " << backup_info.source_database() << " as of "
              << google::cloud::spanner::MakeTimestamp(
                     backup_info.version_time())
                     .value();
  }
  std::cout << " restored to " << restored_db->name();
  std::cout << " from backup " << backup.FullName();
  std::cout << " using encryption key " << encryption_key.FullName();
  std::cout << ".\n";
}

Go


import (
	"context"
	"fmt"
	"io"
	"regexp"

	database "cloud.google.com/go/spanner/admin/database/apiv1"
	adminpb "google.golang.org/genproto/googleapis/spanner/admin/database/v1"
)

func restoreBackupWithCustomerManagedEncryptionKey(ctx context.Context, w io.Writer, db, backupID, kmsKeyName string) error {
	// db = `projects/<project>/instances/<instance-id>/database/<database-id>`
	// backupID = `my-backup-id`
	// kmsKeyName = `projects/<project>/locations/<location>/keyRings/<key_ring>/cryptoKeys/<kms_key_name>`
	matches := regexp.MustCompile("^(.*)/databases/(.*)$").FindStringSubmatch(db)
	if matches == nil || len(matches) != 3 {
		return fmt.Errorf("restoreBackupWithCustomerManagedEncryptionKey: invalid database id %q", db)
	}
	instanceName := matches[1]
	databaseID := matches[2]
	backupName := instanceName + "/backups/" + backupID

	adminClient, err := database.NewDatabaseAdminClient(ctx)
	if err != nil {
		return fmt.Errorf("restoreBackupWithCustomerManagedEncryptionKey.NewDatabaseAdminClient: %w", err)
	}
	defer adminClient.Close()

	// Restore a database from a backup using a Customer Managed Encryption Key.
	restoreOp, err := adminClient.RestoreDatabase(ctx, &adminpb.RestoreDatabaseRequest{
		Parent:     instanceName,
		DatabaseId: databaseID,
		Source: &adminpb.RestoreDatabaseRequest_Backup{
			Backup: backupName,
		},
		EncryptionConfig: &adminpb.RestoreDatabaseEncryptionConfig{
			EncryptionType: adminpb.RestoreDatabaseEncryptionConfig_CUSTOMER_MANAGED_ENCRYPTION,
			KmsKeyName:     kmsKeyName,
		},
	})
	if err != nil {
		return fmt.Errorf("restoreBackupWithCustomerManagedEncryptionKey.RestoreDatabase: %w", err)
	}
	// Wait for restore operation to complete.
	restoredDatabase, err := restoreOp.Wait(ctx)
	if err != nil {
		return fmt.Errorf("restoreBackupWithCustomerManagedEncryptionKey.Wait: %w", err)
	}
	// Get the information from the newly restored database.
	backupInfo := restoredDatabase.RestoreInfo.GetBackupInfo()
	fmt.Fprintf(w, "Database %s restored from backup %s using encryption key %s\n",
		backupInfo.SourceDatabase,
		backupInfo.Backup,
		restoredDatabase.EncryptionConfig.KmsKeyName)

	return nil
}

Java


import static com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION;

import com.google.cloud.spanner.Spanner;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient;
import com.google.spanner.admin.database.v1.BackupName;
import com.google.spanner.admin.database.v1.Database;
import com.google.spanner.admin.database.v1.InstanceName;
import com.google.spanner.admin.database.v1.RestoreDatabaseEncryptionConfig;
import com.google.spanner.admin.database.v1.RestoreDatabaseRequest;
import java.util.concurrent.ExecutionException;

public class RestoreBackupWithEncryptionKey {

  static void restoreBackupWithEncryptionKey() {
    // 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.createDatabaseAdminClient()) {
      restoreBackupWithEncryptionKey(
          adminClient,
          projectId,
          instanceId,
          backupId,
          databaseId,
          kmsKeyName);
    }
  }

  static Void restoreBackupWithEncryptionKey(DatabaseAdminClient adminClient,
      String projectId, String instanceId, String backupId, String restoreId, String kmsKeyName) {
    RestoreDatabaseRequest request =
        RestoreDatabaseRequest.newBuilder()
            .setParent(InstanceName.of(projectId, instanceId).toString())
            .setDatabaseId(restoreId)
            .setBackup(BackupName.of(projectId, instanceId, backupId).toString())
            .setEncryptionConfig(RestoreDatabaseEncryptionConfig.newBuilder()
                .setEncryptionType(CUSTOMER_MANAGED_ENCRYPTION).setKmsKeyName(kmsKeyName)).build();
    Database database;
    try {
      System.out.println("Waiting for operation to complete...");
      database = adminClient.restoreDatabaseAsync(request).get();
      ;
    } 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);
    }

    System.out.printf(
        "Database %s restored to %s from backup %s using encryption key %s%n",
        database.getRestoreInfo().getBackupInfo().getSourceDatabase(),
        database.getName(),
        database.getRestoreInfo().getBackupInfo().getBackup(),
        database.getEncryptionConfig().getKmsKeyName()
    );
    return null;
  }
}

Node.js


// Imports the Google Cloud client library and precise date library
const {Spanner} = require('@google-cloud/spanner');

/**
 * TODO(developer): Uncomment the following lines before running the sample.
 */
// const projectId = 'my-project-id';
// const instanceId = 'my-instance';
// const databaseId = 'my-database';
// const backupId = 'my-backup';
// const keyName =
//   'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key';

// Creates a client
const spanner = new Spanner({
  projectId: projectId,
});

// Gets a reference to a Cloud Spanner Database Admin Client object
const databaseAdminClient = spanner.getDatabaseAdminClient();

// Restore the database
console.log(
  `Restoring database ${databaseAdminClient.databasePath(
    projectId,
    instanceId,
    databaseId
  )} from backup ${backupId}.`
);
const [restoreOperation] = await databaseAdminClient.restoreDatabase({
  parent: databaseAdminClient.instancePath(projectId, instanceId),
  databaseId: databaseId,
  backup: databaseAdminClient.backupPath(projectId, instanceId, backupId),
  encryptionConfig: {
    encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION',
    kmsKeyName: keyName,
  },
});

// Wait for restore to complete
console.log('Waiting for database restore to complete...');
await restoreOperation.promise();

console.log('Database restored from backup.');
const [metadata] = await databaseAdminClient.getDatabase({
  name: databaseAdminClient.databasePath(projectId, instanceId, databaseId),
});
console.log(
  `Database ${metadata.restoreInfo.backupInfo.sourceDatabase} was restored ` +
    `to ${databaseId} from backup ${metadata.restoreInfo.backupInfo.backup} ` +
    `using encryption key ${metadata.encryptionConfig.kmsKeyName}.`
);

PHP

use Google\Cloud\Spanner\Admin\Database\V1\Client\DatabaseAdminClient;
use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseEncryptionConfig;
use Google\Cloud\Spanner\Admin\Database\V1\RestoreDatabaseRequest;

/**
 * Restore a database from a backup.
 * Example:
 * ```
 * restore_backup_with_encryption_key($projectId, $instanceId, $databaseId, $backupId, $kmsKeyName);
 * ```
 * @param string $projectId The Google Cloud project ID.
 * @param string $instanceId The Spanner instance ID.
 * @param string $databaseId The Spanner database ID.
 * @param string $backupId The Spanner backup ID.
 * @param string $kmsKeyName The KMS key used for encryption.
 */
function restore_backup_with_encryption_key(
    string $projectId,
    string $instanceId,
    string $databaseId,
    string $backupId,
    string $kmsKeyName
): void {
    $databaseAdminClient = new DatabaseAdminClient();
    $instanceFullName = DatabaseAdminClient::instanceName($projectId, $instanceId);
    $backupFullName = DatabaseAdminClient::backupName($projectId, $instanceId, $backupId);
    $request = new RestoreDatabaseRequest([
        'parent' => $instanceFullName,
        'database_id' => $databaseId,
        'backup' => $backupFullName,
        'encryption_config' => new RestoreDatabaseEncryptionConfig([
            'kms_key_name' => $kmsKeyName,
            'encryption_type' => RestoreDatabaseEncryptionConfig\EncryptionType::CUSTOMER_MANAGED_ENCRYPTION
        ])
    ]);

    // Create restore operation
    $operation = $databaseAdminClient->restoreDatabase($request);

    print('Waiting for operation to complete...' . PHP_EOL);
    $operation->pollUntilComplete();

    // Reload new database and get restore info
    $database = $operation->operationSucceeded() ? $operation->getResult() : null;
    $restoreInfo = $database->getRestoreInfo();
    $backupInfo = $restoreInfo->getBackupInfo();
    $sourceDatabase = $backupInfo->getSourceDatabase();
    $sourceBackup = $backupInfo->getBackup();
    $encryptionConfig = $database->getEncryptionConfig();
    printf(
        'Database %s restored from backup %s using encryption key %s' . PHP_EOL,
        $sourceDatabase, $sourceBackup, $encryptionConfig->getKmsKeyName()
    );
}

Python

def restore_database_with_encryption_key(
    instance_id, new_database_id, backup_id, kms_key_name
):
    """Restores a database from a backup using a Customer Managed Encryption Key (CMEK)."""
    from google.cloud.spanner_admin_database_v1 import (
        RestoreDatabaseEncryptionConfig, RestoreDatabaseRequest)

    spanner_client = spanner.Client()
    database_admin_api = spanner_client.database_admin_api

    # Start restoring an existing backup to a new database.
    encryption_config = {
        "encryption_type": RestoreDatabaseEncryptionConfig.EncryptionType.CUSTOMER_MANAGED_ENCRYPTION,
        "kms_key_name": kms_key_name,
    }

    request = RestoreDatabaseRequest(
        parent=database_admin_api.instance_path(spanner_client.project, instance_id),
        database_id=new_database_id,
        backup=database_admin_api.backup_path(
            spanner_client.project, instance_id, backup_id
        ),
        encryption_config=encryption_config,
    )
    operation = database_admin_api.restore_database(request)

    # Wait for restore operation to complete.
    db = operation.result(1600)

    # Newly created database has restore information.
    restore_info = db.restore_info
    print(
        "Database {} restored to {} from backup {} with using encryption key {}.".format(
            restore_info.backup_info.source_database,
            new_database_id,
            restore_info.backup_info.backup,
            db.encryption_config.kms_key_name,
        )
    )

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 鍵の監査ログを表示する

  1. プロジェクト内の Cloud KMS API でロギングが有効になっていることを確認します。

  2. Cloud Console のログ エクスプローラに移動します。

    Cloud コンソールに移動

  3. クエリビルダーに次の行を追加して、ログエントリを 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"
    
  4. 通常のオペレーションでは、暗号化アクションと復号アクションは INFO 重大度でログに記録されます。これらのエントリは、Spanner インスタンスのゾーンとしてログに記録され、約 5 分ごとに Cloud KMS 鍵をポーリングします。

  5. Spanner が鍵へのアクセスに失敗すると、そのオペレーションは ERROR としてログに記録されます。