고객 관리 암호화 키(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에서 서비스 에이전트를 만들어 표시하거나, 계정이 이미 있으면 표시합니다.

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

      gcloud 베타 명령어 구성요소를 설치하라는 메시지가 표시되면 Y를 입력합니다. 설치가 완료되면 명령어가 자동으로 다시 시작됩니다.

      gcloud services identity 명령어는 Spanner가 사용자 대신 Cloud KMS 키에 액세스하기 위해 사용할 수 있는 서비스 에이전트를 만들거나 가져옵니다.

      서비스 계정 ID의 형식은 이메일 주소와 비슷합니다.

      Service identity created: service-xxx@gcp-sa-spanner.iam.gserviceaccount.com
      
    2. 서비스 계정에 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 키를 지정합니다.

    콘솔

    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에 즉시 전파되지 않습니다. 변경사항이 정보 필드에 반영되기 위해서는 지연이 필요할 수 있습니다.

콘솔

암호화 정보는 데이터베이스 세부정보 페이지에 표시됩니다.

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'
    

    변경사항이 적용되었으면 명령어가 성공적으로 실행됩니다.

데이터베이스 백업

기본적으로 데이터베이스에서 생성된 백업에는 데이터베이스 자체와 동일한 암호화 구성이 사용됩니다. 선택적으로 백업에 다른 암호화 구성을 지정할 수 있습니다.

백업을 만들려면 다음 안내를 따르세요.

콘솔

  1. Google Cloud 콘솔에서 데이터베이스 세부정보 페이지로 이동합니다.

    Cloud 콘솔로 이동

  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  \
    --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++

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로 보호되는 경우 백업을 만들기 위해 사용된 키 버전을 복호화 작업에 사용할 수 있어야 합니다.

데이터베이스를 복원하려면:

콘솔

  1. Cloud 콘솔에서 인스턴스 세부정보 페이지로 이동합니다.

    Cloud 콘솔로 이동

  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 콘솔의 로그 탐색기로 이동합니다.

    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로 로깅됩니다.