使用 Cloud KMS 密钥保护数据

默认情况下,BigQuery 会对以静态方式存储的客户内容进行加密。 BigQuery 会为您处理和管理这项默认加密作业,您无需进行任何其他操作。首先,系统会使用“数据加密密钥”(DEK) 对 BigQuery 表中的数据进行加密。然后,通过"密钥加密密钥" (KEK) 来对 DEK 进行加密(这称为信封加密)。KEK 不用于直接加密您的数据,而是用于对 Google 用来加密您数据的 DEK 进行加密。如需了解详情,请参阅密钥管理

如果您想自行控制加密,可为 BigQuery 使用客户管理的加密密钥 (CMEK)。您自行控制和管理 Cloud KMS 中保护您数据的密钥加密密钥 (KEK),而不是由 Google 管理。本主题将提供有关这一用法的详细信息。

详细了解 Google Cloud 的加密选项

准备工作

  1. 了解数据集查询

  2. 确定您要在同一个 Google Cloud 项目中还是在不同项目中运行 BigQuery 和 Cloud KMS。为了方便说明,本文的示例将采用下列惯例:

    • PROJECT_ID:运行 BigQuery 的项目的 ID
    • PROJECT_NUMBER:运行 BigQuery 的项目的编号
    • KMS_PROJECT_ID:运行 Cloud KMS 的项目的 ID(即使该项目就是运行 BigQuery 的同一个项目)
    如需了解 Cloud 项目 ID 和项目编号,请参阅识别项目

  3. 新项目中会自动启用 BigQuery。如果您使用现有项目来运行 BigQuery,请启用 BigQuery API

  4. 对于运行 Cloud KMS 的 Cloud 项目,请执行以下操作:

    1. 启用 Cloud Key Management Service API
    2. 按照创建密钥环和密钥中的说明创建密钥环和密钥。请在与 BigQuery 数据集的位置匹配的位置创建密钥环:
      • 任何多区域数据集都应使用来自匹配位置的多区域密钥环。例如,应使用区域 us 中的密钥环保护位于区域 US 的数据集,使用区域 europe 中的密钥环保护位于区域 EU 的数据集。
      • 区域数据集应使用匹配的区域密钥。例如,应使用区域 asia-northeast1 中的密钥环保护位于区域 asia-northeast1 的数据集。
      • global 区域不支持使用 BigQuery。
      如需详细了解支持 BigQuery 和 Cloud KMS 的位置,请参阅 Cloud 位置

加密规范

用于保护您在 BigQuery 中的数据的 Cloud KMS 密钥是 AES-256 密钥。这类密钥在 BigQuery 中被用作 KEK,其用途是对加密您数据的 DEK 进行加密。

授予加密和解密权限

要使用 CMEK 密钥保护您的 BigQuery 数据,请向 BigQuery 服务帐号授予使用该密钥进行加密和解密的权限。Cloud KMS CryptoKey Encrypter/Decrypter 角色可授予此权限。

使用 Google Cloud Console 确定 BigQuery 服务帐号 ID,并为服务帐号提供适当的角色,以使用 Cloud KMS 进行加密和解密。

确定服务帐号 ID

BigQuery 服务帐号 ID 的格式如下:

bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com

以下方法展示了如何确定您项目的 BigQuery 服务帐号 ID。

控制台

  1. 转到 Cloud Console 中的信息中心页面

    转到“信息中心”页面

  2. 点击页面顶部的选择项目下拉列表。在出现的选择项目窗口中,选择您的项目。

  3. 项目 ID 和项目编号显示在项目信息中心的项目信息卡片上:

    项目信息卡片

  4. 在以下字符串中,将 PROJECT_NUMBER 替换为您的项目编号。新字符串会识别您的 BigQuery 服务帐号 ID。

    bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com
    

bq

使用带有 --encryption_service_account 标志的 bq show 命令确定服务帐号 ID:

bq show --encryption_service_account

该命令显示服务帐号 ID:

                  ServiceAccountID
-------------------------------------------------------------
bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com

分配 Encrypter/Decrypter 角色

将 Cloud KMS CryptoKey Encrypter/Decrypter 角色分配给您已复制到剪贴板的 BigQuery 系统服务帐号。该帐号的格式如下:

bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com

控制台

  1. 在 Cloud Console 中,打开加密密钥页面。

    打开“加密密钥”页面

  2. 点击包含密钥的密钥环的名称。

  3. 点击要添加角色的加密密钥对应的复选框。此时会打开权限标签。

  4. 点击添加成员

  5. 输入服务帐号的电子邮件地址 bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com

    • 如果该服务帐号已经在成员列表中,则表明它具有现有角色。点击 bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com 服务帐号的当前角色下拉列表。
  6. 点击选择角色下拉列表,点击 Cloud KMS,然后点击 Cloud KMS CryptoKey Encrypter/Decrypter 角色。

  7. 点击保存,将角色应用到 bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com 服务帐号。

gcloud

您可以使用 gcloud 命令行工具分配该角色:

gcloud kms keys add-iam-policy-binding \
--project=KMS_PROJECT_ID \
--member serviceAccount:bq-PROJECT_NUMBER@bigquery-encryption.iam.gserviceaccount.com \
--role roles/cloudkms.cryptoKeyEncrypterDecrypter \
--location=KMS_KEY_LOCATION \
--keyring=KMS_KEY_RING \
KMS_KEY

替换以下内容:

  • KMS_PROJECT_ID:运行 Cloud KMS 的 Google Cloud 项目的 ID
  • PROJECT_NUMBER:运行 BigQuery 的 Cloud 项目的编号(而非项目 ID)
  • KMS_KEY_LOCATION:Cloud KMS 密钥的位置名称
  • KMS_KEY_RING:Cloud KMS 密钥的密钥环名称
  • KMS_KEY:Cloud KMS 密钥的名称

密钥资源 ID

使用 CMEK 时需要 Cloud KMS 密钥的资源 ID,如本主题的示例所示。此密钥区分大小写,格式如下:

projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY

检索密钥资源 ID

  1. 在 Cloud Console 中,打开加密密钥页面。

    打开“加密密钥”页面

  2. 点击包含密钥的密钥环的名称。

  3. 找到要检索其资源 ID 的密钥,点击更多

  4. 点击复制资源 ID。密钥的资源 ID 会复制到剪贴板。

创建受 Cloud KMS 保护的表

创建受 Cloud KMS 保护的空表

要创建受 Cloud KMS 保护的表,请执行以下操作:

控制台

  1. 在 Cloud Console 中打开 BigQuery 页面。

    转到 BigQuery 页面

  2. 在导航面板的资源部分中,展开您的项目并选择数据集。

  3. 点击创建表

  4. Create table 页面上,填写创建具有架构定义的空表所需的信息。在您点击 Create table 之前,请设置加密类型并指定要用于表的 Cloud KMS 密钥:

    1. 点击高级选项
    2. 点击客户管理的密钥
    3. 选择密钥。如果要使用的密钥未列出,请输入密钥的资源 ID
  5. 点击创建表

bq

您可以将 bq 命令行工具与 --destination_kms_key 标志结合使用以创建表。--destination_kms_key 标志指定用于表的密钥的资源 ID

如需创建具有架构的空表,请使用以下命令:

bq mk --schema name:string,value:integer -t \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
mydataset.newtable

或者,您可以使用 DDL 语句:

bq query --use_legacy_sql=false "
  CREATE TABLE mydataset.newtable (name STRING, value INT64)
  OPTIONS(
    kms_key_name='projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY'
  )
"

如需基于查询创建表,请使用以下命令:

bq query --destination_table=mydataset.newtable \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
"SELECT name,count FROM mydataset.babynames WHERE gender = 'M' ORDER BY count DESC LIMIT 6"

如需详细了解 bq 命令行工具,请参阅 使用 bq 命令行工具

Go

import (
	"context"
	"fmt"

	"cloud.google.com/go/bigquery"
)

// createTableWithCMEK demonstrates creating a table protected with a customer managed encryption key.
func createTableWithCMEK(projectID, datasetID, tableID string) error {
	// projectID := "my-project-id"
	// datasetID := "mydatasetid"
	// tableID := "mytableid"
	ctx := context.Background()

	client, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("bigquery.NewClient: %v", err)
	}
	defer client.Close()

	tableRef := client.Dataset(datasetID).Table(tableID)
	meta := &bigquery.TableMetadata{
		EncryptionConfig: &bigquery.EncryptionConfig{
			// TODO: Replace this key with a key you have created in Cloud KMS.
			KMSKeyName: "projects/cloud-samples-tests/locations/us/keyRings/test/cryptoKeys/test",
		},
	}
	if err := tableRef.Create(ctx, meta); err != nil {
		return err
	}
	return nil
}

Java

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.EncryptionConfiguration;
import com.google.cloud.bigquery.Field;
import com.google.cloud.bigquery.Schema;
import com.google.cloud.bigquery.StandardSQLTypeName;
import com.google.cloud.bigquery.StandardTableDefinition;
import com.google.cloud.bigquery.TableDefinition;
import com.google.cloud.bigquery.TableId;
import com.google.cloud.bigquery.TableInfo;

// Sample to create a cmek table
public class CreateTableCMEK {

  public static void runCreateTableCMEK() {
    // TODO(developer): Replace these variables before running the sample.
    String datasetName = "MY_DATASET_NAME";
    String tableName = "MY_TABLE_NAME";
    String kmsKeyName = "MY_KEY_NAME";
    Schema schema =
        Schema.of(
            Field.of("stringField", StandardSQLTypeName.STRING),
            Field.of("booleanField", StandardSQLTypeName.BOOL));
    // i.e. projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{cryptoKey}
    EncryptionConfiguration encryption =
        EncryptionConfiguration.newBuilder().setKmsKeyName(kmsKeyName).build();
    createTableCMEK(datasetName, tableName, schema, encryption);
  }

  public static void createTableCMEK(
      String datasetName, String tableName, Schema schema, EncryptionConfiguration configuration) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      TableId tableId = TableId.of(datasetName, tableName);
      TableDefinition tableDefinition = StandardTableDefinition.of(schema);
      TableInfo tableInfo =
          TableInfo.newBuilder(tableId, tableDefinition)
              .setEncryptionConfiguration(configuration)
              .build();

      bigquery.create(tableInfo);
      System.out.println("Table cmek created successfully");
    } catch (BigQueryException e) {
      System.out.println("Table cmek was not created. \n" + e.toString());
    }
  }
}

Python

在创建表之前,将 Table.encryption_configuration 属性设置为 EncryptionConfiguration 对象,以使用 CMEK 保护新表。

# from google.cloud import bigquery
# client = bigquery.Client()
# dataset_id = 'my_dataset'

table_ref = dataset.table("my_table")
table = bigquery.Table(table_ref)

# Set the encryption key to use for the table.
# TODO: Replace this key with a key you have created in Cloud KMS.
kms_key_name = "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format(
    "cloud-samples-tests", "us", "test", "test"
)
table.encryption_configuration = bigquery.EncryptionConfiguration(
    kms_key_name=kms_key_name
)

table = client.create_table(table)  # API request

assert table.encryption_configuration.kms_key_name == kms_key_name

查询受 Cloud KMS 密钥保护的表

查询受 Cloud KMS 保护的表无需特殊操作安排。BigQuery 会存储用于加密表内容的密钥的名称,并在受 Cloud KMS 保护的表被查询时使用该密钥。

只要 BigQuery 有权访问用于加密表内容的 Cloud KMS 密钥,则所有现有工具、BigQuery 控制台和 bq 命令行工具的运行方式都将与默认加密表相同。

使用 Cloud KMS 密钥保护查询结果

控制台

  1. 在 Cloud Console 中打开 BigQuery 页面。

    转到 BigQuery 页面

  2. 点击编写新查询

  3. 在查询文本区域中输入有效的 BigQuery SQL 查询。

  4. 点击更多,点击查询设置,然后点击高级选项

  5. 选择客户管理的加密

  6. 选择密钥。如果要使用的密钥未列出,请输入密钥的资源 ID

  7. 点击保存

  8. 点击运行

bq

使用 Cloud KMS 密钥指定标记 --destination_kms_key 以保护目标表或查询结果(如果使用临时表)。--destination_kms_key 标志指定用于目标表或生成的表的密钥的资源 ID

您可以选择使用 --destination_table 标志为查询结果指定目标。如果未使用 --destination_table,查询结果将写入临时表中。

如需查询表,请使用以下命令:

bq query \
--destination_table=mydataset.newtable \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
"SELECT name,count FROM mydataset.babynames WHERE gender = 'M' ORDER BY count DESC LIMIT 6"

如需详细了解 bq 命令行工具,请参阅 使用 bq 命令行工具

Go

import (
	"context"
	"fmt"
	"io"

	"cloud.google.com/go/bigquery"
	"google.golang.org/api/iterator"
)

// queryWithDestinationCMEK demonstrates saving query results to a destination table and protecting those results
// by specifying a customer managed encryption key.
func queryWithDestinationCMEK(w io.Writer, projectID, dstDatasetID, dstTableID string) error {
	// projectID := "my-project-id"
	// datasetID := "mydataset"
	// tableID := "mytable"
	ctx := context.Background()
	client, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("bigquery.NewClient: %v", err)
	}
	defer client.Close()

	q := client.Query("SELECT 17 as my_col")
	q.Location = "US" // Location must match the dataset(s) referenced in query.
	q.QueryConfig.Dst = client.Dataset(dstDatasetID).Table(dstTableID)
	q.DestinationEncryptionConfig = &bigquery.EncryptionConfig{
		// TODO: Replace this key with a key you have created in Cloud KMS.
		KMSKeyName: "projects/cloud-samples-tests/locations/us-central1/keyRings/test/cryptoKeys/test",
	}
	// Run the query and print results when the query job is completed.
	job, err := q.Run(ctx)
	if err != nil {
		return err
	}
	status, err := job.Wait(ctx)
	if err != nil {
		return err
	}
	if err := status.Err(); err != nil {
		return err
	}
	it, err := job.Read(ctx)
	for {
		var row []bigquery.Value
		err := it.Next(&row)
		if err == iterator.Done {
			break
		}
		if err != nil {
			return err
		}
		fmt.Fprintln(w, row)
	}
	return nil
}

Java

试用此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Java 设置说明进行操作。 如需了解详情,请参阅 BigQuery Java API 参考文档

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.EncryptionConfiguration;
import com.google.cloud.bigquery.QueryJobConfiguration;
import com.google.cloud.bigquery.TableResult;

// Sample to query on destination table with encryption key
public class QueryDestinationTableCMEK {

  public static void runQueryDestinationTableCMEK() {
    // TODO(developer): Replace these variables before running the sample.
    String datasetName = "MY_DATASET_NAME";
    String tableName = "MY_TABLE_NAME";
    String kmsKeyName = "MY_KMS_KEY_NAME";
    String query =
        String.format("SELECT stringField, booleanField FROM %s.%s", datasetName, tableName);
    EncryptionConfiguration encryption =
        EncryptionConfiguration.newBuilder().setKmsKeyName(kmsKeyName).build();
    queryDestinationTableCMEK(query, encryption);
  }

  public static void queryDestinationTableCMEK(String query, EncryptionConfiguration encryption) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      QueryJobConfiguration config =
          QueryJobConfiguration.newBuilder(query)
              // Set the encryption key to use for the destination.
              .setDestinationEncryptionConfiguration(encryption)
              .build();

      TableResult results = bigquery.query(config);

      results
          .iterateAll()
          .forEach(row -> row.forEach(val -> System.out.printf("%s,", val.toString())));
      System.out.println("Query performed successfully with encryption key.");
    } catch (BigQueryException | InterruptedException e) {
      System.out.println("Query not performed \n" + e.toString());
    }
  }
}

Python

from google.cloud import bigquery

# Construct a BigQuery client object.
client = bigquery.Client()

# TODO(developer): Set table_id to the ID of the destination table.
# table_id = "your-project.your_dataset.your_table_name"

# Set the encryption key to use for the destination.
# TODO(developer): Replace this key with a key you have created in KMS.
# kms_key_name = "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format(
#     your-project, location, your-ring, your-key
# )

job_config = bigquery.QueryJobConfig(
    destination=table_id,
    destination_encryption_configuration=bigquery.EncryptionConfiguration(
        kms_key_name=kms_key_name
    ),
)

# Start the query, passing in the extra configuration.
query_job = client.query(
    "SELECT 17 AS my_col;", job_config=job_config
)  # Make an API request.
query_job.result()  # Wait for the job to complete.

table = client.get_table(table_id)  # Make an API request.
if table.encryption_configuration.kms_key_name == kms_key_name:
    print("The destination table is written using the encryption configuration")

加载受 Cloud KMS 保护的表

要将数据文件加载到受 Cloud KMS 保护的表中,请使用以下方式:

控制台

在加载表时指定密钥,以使用 CMEK 保护加载作业的目标表。

  1. 在 Cloud Console 中打开 BigQuery 页面。

    转到 BigQuery 页面

  2. 在导航面板的资源部分中,展开您的项目并选择数据集。

  3. 在窗口右侧的详细信息面板中,点击创建表

  4. 输入要用于加载表的选项,但在点击创建表之前,请先点击高级选项

  5. 加密下选择客户管理的密钥

  6. 点击选择客户管理的密钥下拉列表,然后选择要使用的密钥。如果您没有看到任何可用的密钥,请输入密钥资源 ID

    高级选项。

  7. 点击创建表

bq

设置 --destination_kms_key 标志,以使用 CMEK 保护加载作业的目标表。

bq --location=LOCATION load \
--autodetect \
--source_format=FORMAT \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
DATASET.TABLE \
path_to_source
例如:
bq load \
--autodetect \
--source_format=NEWLINE_DELIMITED_JSON \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
test2.table4 \
gs://cloud-samples-data/bigquery/us-states/us-states.json

Go

import (
	"context"
	"fmt"

	"cloud.google.com/go/bigquery"
)

// importJSONWithCMEK demonstrates loading newline-delimited JSON from Cloud Storage,
// and protecting the data with a customer-managed encryption key.
func importJSONWithCMEK(projectID, datasetID, tableID string) error {
	// projectID := "my-project-id"
	// datasetID := "mydataset"
	// tableID := "mytable"
	ctx := context.Background()
	client, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("bigquery.NewClient: %v", err)
	}
	defer client.Close()

	gcsRef := bigquery.NewGCSReference("gs://cloud-samples-data/bigquery/us-states/us-states.json")
	gcsRef.SourceFormat = bigquery.JSON
	gcsRef.AutoDetect = true
	loader := client.Dataset(datasetID).Table(tableID).LoaderFrom(gcsRef)
	loader.WriteDisposition = bigquery.WriteEmpty
	loader.DestinationEncryptionConfig = &bigquery.EncryptionConfig{
		// TODO: Replace this key with a key you have created in KMS.
		KMSKeyName: "projects/cloud-samples-tests/locations/us-central1/keyRings/test/cryptoKeys/test",
	}

	job, err := loader.Run(ctx)
	if err != nil {
		return err
	}
	status, err := job.Wait(ctx)
	if err != nil {
		return err
	}

	if status.Err() != nil {
		return fmt.Errorf("job completed with error: %v", status.Err())
	}

	return nil
}

Java

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.EncryptionConfiguration;
import com.google.cloud.bigquery.FormatOptions;
import com.google.cloud.bigquery.Job;
import com.google.cloud.bigquery.JobInfo;
import com.google.cloud.bigquery.LoadJobConfiguration;
import com.google.cloud.bigquery.TableId;

// Sample to load JSON data with configuration key from Cloud Storage into a new BigQuery table
public class LoadJsonFromGCSCMEK {

  public static void runLoadJsonFromGCSCMEK() {
    // TODO(developer): Replace these variables before running the sample.
    String datasetName = "MY_DATASET_NAME";
    String tableName = "MY_TABLE_NAME";
    String kmsKeyName = "MY_KMS_KEY_NAME";
    String sourceUri = "gs://cloud-samples-data/bigquery/us-states/us-states.json";
    // i.e. projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{cryptoKey}
    EncryptionConfiguration encryption =
        EncryptionConfiguration.newBuilder().setKmsKeyName(kmsKeyName).build();
    loadJsonFromGCSCMEK(datasetName, tableName, sourceUri, encryption);
  }

  public static void loadJsonFromGCSCMEK(
      String datasetName, String tableName, String sourceUri, EncryptionConfiguration encryption) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      TableId tableId = TableId.of(datasetName, tableName);
      LoadJobConfiguration loadConfig =
          LoadJobConfiguration.newBuilder(tableId, sourceUri)
              // Set the encryption key to use for the destination.
              .setDestinationEncryptionConfiguration(encryption)
              .setFormatOptions(FormatOptions.json())
              .setAutodetect(true)
              .build();

      // Load data from a GCS JSON file into the table
      Job job = bigquery.create(JobInfo.of(loadConfig));
      // Blocks until this load table job completes its execution, either failing or succeeding.
      job = job.waitFor();
      if (job.isDone()) {
        System.out.println("Table loaded succesfully from GCS with configuration key");
      } else {
        System.out.println(
            "BigQuery was unable to load into the table due to an error:"
                + job.getStatus().getError());
      }
    } catch (BigQueryException | InterruptedException e) {
      System.out.println("Column not added during load append \n" + e.toString());
    }
  }
}

Python

LoadJobConfig.destination_encryption_configuration 属性设置为 EncryptionConfiguration 并加载表,以使用 CMEK 保护加载作业的目标表。

from google.cloud import bigquery

# Construct a BigQuery client object.
client = bigquery.Client()

# TODO(developer): Set table_id to the ID of the table to create.
# table_id = "your-project.your_dataset.your_table_name

# Set the encryption key to use for the destination.
# TODO: Replace this key with a key you have created in KMS.
# kms_key_name = "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format(
#     "cloud-samples-tests", "us", "test", "test"
# )

job_config = bigquery.LoadJobConfig(
    autodetect=True,
    source_format=bigquery.SourceFormat.NEWLINE_DELIMITED_JSON,
    destination_encryption_configuration=bigquery.EncryptionConfiguration(
        kms_key_name=kms_key_name
    ),
)

uri = "gs://cloud-samples-data/bigquery/us-states/us-states.json"

load_job = client.load_table_from_uri(
    uri,
    table_id,
    location="US",  # Must match the destination dataset location.
    job_config=job_config,
)  # Make an API request.

assert load_job.job_type == "load"

load_job.result()  # Waits for the job to complete.

assert load_job.state == "DONE"
table = client.get_table(table_id)

if table.encryption_configuration.kms_key_name == kms_key_name:
    print("A table loaded with encryption configuration key")

将数据流式插入受 Cloud KMS 保护的表

您可以将数据流式插入受 CMEK 保护的 BigQuery 表中,而无需指定任何其他参数。请注意,系统将使用 Cloud KMS 密钥对缓冲区和最终位置中的此类数据进行加密。在将数据流式插入到 CMEK 表之前,请先查看密钥可用性和可访问性上所述的要求。

如需详细了解流式插入,请参阅将数据流式插入 BigQuery 中

将表从默认加密更改为 Cloud KMS 保护

bq

您可以使用带 --destination_kms_key 标记的 bq cp 命令将受默认加密保护的表复制到新表或受 Cloud KMS 保护的原始表中。--destination_kms_key 标志指定要用于目标表的密钥的资源 ID

要将采用默认加密方式的表复制到受 Cloud KMS 保护的新表,请使用以下命令:

bq cp \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
sourceDataset.sourceTableId destinationDataset.destinationTableId

要将采用默认加密方式的表复制到受 Cloud KMS 保护的相同表,请使用以下命令:

bq cp -f \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
sourceDataset.sourceTableId sourceDataset.sourceTableId

如果您想将表格从 Cloud KMS 保护更改为默认加密,请在不使用 --destination_kms_key 标记的情况下运行 bq cp,将文件复制到自身。

如需详细了解 bq 命令行工具,请参阅 使用 bq 命令行工具

Go

import (
	"context"
	"fmt"

	"cloud.google.com/go/bigquery"
)

// copyTableWithCMEK demonstrates creating a copy of a table and ensuring the copied data is
// protected with a customer managed encryption key.
func copyTableWithCMEK(projectID, datasetID, tableID string) error {
	// projectID := "my-project-id"
	// datasetID := "mydataset"
	// tableID := "mytable"
	ctx := context.Background()
	client, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("bigquery.NewClient: %v", err)
	}
	defer client.Close()

	srcTable := client.DatasetInProject("bigquery-public-data", "samples").Table("shakespeare")
	copier := client.Dataset(datasetID).Table(tableID).CopierFrom(srcTable)
	copier.DestinationEncryptionConfig = &bigquery.EncryptionConfig{
		// TODO: Replace this key with a key you have created in Cloud KMS.
		KMSKeyName: "projects/cloud-samples-tests/locations/us-central1/keyRings/test/cryptoKeys/test",
	}
	job, err := copier.Run(ctx)
	if err != nil {
		return err
	}
	status, err := job.Wait(ctx)
	if err != nil {
		return err
	}
	if err := status.Err(); err != nil {
		return err
	}
	return nil
}

Java

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.CopyJobConfiguration;
import com.google.cloud.bigquery.EncryptionConfiguration;
import com.google.cloud.bigquery.Job;
import com.google.cloud.bigquery.JobInfo;
import com.google.cloud.bigquery.TableId;

// Sample to copy a cmek table
public class CopyTableCMEK {

  public static void runCopyTableCMEK() {
    // TODO(developer): Replace these variables before running the sample.
    String destinationDatasetName = "MY_DESTINATION_DATASET_NAME";
    String destinationTableId = "MY_DESTINATION_TABLE_NAME";
    String sourceDatasetName = "MY_SOURCE_DATASET_NAME";
    String sourceTableId = "MY_SOURCE_TABLE_NAME";
    String kmsKeyName = "MY_KMS_KEY_NAME";
    EncryptionConfiguration encryption =
        EncryptionConfiguration.newBuilder().setKmsKeyName(kmsKeyName).build();
    copyTableCMEK(
        sourceDatasetName, sourceTableId, destinationDatasetName, destinationTableId, encryption);
  }

  public static void copyTableCMEK(
      String sourceDatasetName,
      String sourceTableId,
      String destinationDatasetName,
      String destinationTableId,
      EncryptionConfiguration encryption) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      TableId sourceTable = TableId.of(sourceDatasetName, sourceTableId);
      TableId destinationTable = TableId.of(destinationDatasetName, destinationTableId);

      // For more information on CopyJobConfiguration see:
      // https://googleapis.dev/java/google-cloud-clients/latest/com/google/cloud/bigquery/JobConfiguration.html
      CopyJobConfiguration configuration =
          CopyJobConfiguration.newBuilder(destinationTable, sourceTable)
              .setDestinationEncryptionConfiguration(encryption)
              .build();

      // For more information on Job see:
      // https://googleapis.dev/java/google-cloud-clients/latest/index.html?com/google/cloud/bigquery/package-summary.html
      Job job = bigquery.create(JobInfo.of(configuration));

      // Blocks until this job completes its execution, either failing or succeeding.
      Job completedJob = job.waitFor();
      if (completedJob == null) {
        System.out.println("Job not executed since it no longer exists.");
        return;
      } else if (completedJob.getStatus().getError() != null) {
        System.out.println(
            "BigQuery was unable to copy table due to an error: \n" + job.getStatus().getError());
        return;
      }
      System.out.println("Table cmek copied successfully.");
    } catch (BigQueryException | InterruptedException e) {
      System.out.println("Table cmek copying job was interrupted. \n" + e.toString());
    }
  }
}

Python

在尝试此示例之前,请按照《BigQuery 快速入门:使用客户端库》中的 Python 设置说明进行操作。如需了解详情,请参阅 BigQuery Python API 参考文档

QueryJobConfig.destination_encryption_configuration 属性设置为 EncryptionConfiguration 并复制表,以使用 CMEK 保护表的副本目标。

from google.cloud import bigquery

# Construct a BigQuery client object.
client = bigquery.Client()

# TODO(developer): Set dest_table_id to the ID of the destination table.
# dest_table_id = "your-project.your_dataset.your_table_name"

# TODO(developer): Set orig_table_id to the ID of the original table.
# orig_table_id = "your-project.your_dataset.your_table_name"

# Set the encryption key to use for the destination.
# TODO(developer): Replace this key with a key you have created in KMS.
# kms_key_name = "projects/{}/locations/{}/keyRings/{}/cryptoKeys/{}".format(
#     your-project, location, your-ring, your-key
# )

job_config = bigquery.CopyJobConfig(
    destination_encryption_configuration=bigquery.EncryptionConfiguration(
        kms_key_name=kms_key_name
    )
)
job = client.copy_table(orig_table_id, dest_table_id, job_config=job_config)
job.result()  # Wait for the job to complete.

dest_table = client.get_table(dest_table_id)  # Make an API request.
if dest_table.encryption_configuration.kms_key_name == kms_key_name:
    print("A copy of the table created")

确定表是否受 Cloud KMS 保护

  1. 在 Cloud Console 中,点击数据集左侧的蓝色箭头以展开数据集,或双击数据集名称。这将显示数据集中的表和视图。

  2. 点击表名称。

  3. 点击 DetailsTable Details 页面会显示表说明和表信息。

  4. 如果表受 Cloud KMS 保护,则 Customer-Managed Encryption Key 字段将显示密钥资源 ID。

    受保护的表。

更改 BigQuery 表的 Cloud KMS 密钥

要更改现有受 CMEK 保护的表的 Cloud KMS 密钥,您可以运行 ALTER TABLE 查询,使用 API 或使用 bq 命令行工具。您可以通过以下两种方式使用 API 和 bq 命令行工具来修改 Cloud KMS 密钥:updatecp。如果使用 update,则可以更改用于受 KMS 保护的表的 Cloud KMS 密钥。如果使用 cp,则可以更改用于受 CMEK 保护的表的 Cloud KMS 密钥,将表从默认加密更改为 CMEK 保护,也可以将表从 CMEK 保护更改为默认加密。update 的一个优点是速度比 cp 更快,并且支持使用表修饰器

控制台

  1. 在 Cloud Console 中打开 BigQuery 页面。

    转到 BigQuery 页面

  2. 点击编写新查询

  3. New Query 文本区域中输入 DDL 语句。对于 kms_key_name 值,请指定要用于保护表的密钥的资源 ID

    #standardSQL
    ALTER TABLE mydataset.mytable
    SET OPTIONS (
    kms_key_name="projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY"
    )
    
  4. 点击运行

bq

您可以将 bq cp 命令与 --destination_kms_key 标志一起使用,以更改受 Cloud KMS 保护的表的密钥。--destination_kms_key 标志指定要用于表的密钥的资源 ID

bq update \
--destination_kms_key projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY \
-t DATASET_ID.TABLE_ID

Go

import (
	"context"
	"fmt"

	"cloud.google.com/go/bigquery"
)

// updateTableChangeCMEK demonstrates how to change the customer managed encryption key that protects a table.
func updateTableChangeCMEK(projectID, datasetID, tableID string) error {
	// projectID := "my-project-id"
	// datasetID := "mydatasetid"
	// tableID := "mytableid"
	ctx := context.Background()

	client, err := bigquery.NewClient(ctx, projectID)
	if err != nil {
		return fmt.Errorf("bigquery.NewClient: %v", err)
	}
	defer client.Close()

	tableRef := client.Dataset(datasetID).Table(tableID)
	meta, err := tableRef.Metadata(ctx)
	if err != nil {
		return err
	}
	update := bigquery.TableMetadataToUpdate{
		EncryptionConfig: &bigquery.EncryptionConfig{
			// TODO: Replace this key with a key you have created in Cloud KMS.
			KMSKeyName: "projects/cloud-samples-tests/locations/us-central1/keyRings/test/cryptoKeys/otherkey",
		},
	}
	if _, err := tableRef.Update(ctx, update, meta.ETag); err != nil {
		return err
	}
	return nil
}

Java

import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQueryException;
import com.google.cloud.bigquery.BigQueryOptions;
import com.google.cloud.bigquery.EncryptionConfiguration;
import com.google.cloud.bigquery.Table;
import com.google.cloud.bigquery.TableId;

// Sample to update a cmek table
public class UpdateTableCMEK {

  public static void runUpdateTableCMEK() {
    // TODO(developer): Replace these variables before running the sample.
    String datasetName = "MY_DATASET_NAME";
    String tableName = "MY_TABLE_NAME";
    String kmsKeyName = "MY_KEY_NAME";
    // Set a new encryption key to use for the destination.
    // i.e. projects/{project}/locations/{location}/keyRings/{key_ring}/cryptoKeys/{cryptoKey}
    EncryptionConfiguration encryption =
        EncryptionConfiguration.newBuilder().setKmsKeyName(kmsKeyName).build();
    updateTableCMEK(datasetName, tableName, encryption);
  }

  public static void updateTableCMEK(
      String datasetName, String tableName, EncryptionConfiguration encryption) {
    try {
      // Initialize client that will be used to send requests. This client only needs to be created
      // once, and can be reused for multiple requests.
      BigQuery bigquery = BigQueryOptions.getDefaultInstance().getService();

      Table table = bigquery.getTable(TableId.of(datasetName, tableName));
      bigquery.update(table.toBuilder().setEncryptionConfiguration(encryption).build());
      System.out.println("Table cmek updated successfully");
    } catch (BigQueryException e) {
      System.out.println("Table cmek was not updated. \n" + e.toString());
    }
  }
}

Python

Table.encryption_configuration 属性更改为新的 EncryptionConfiguration 对象并更新该表,以更改表的 CMEK。

# from google.cloud import bigquery
# client = bigquery.Client()

assert table.encryption_configuration.kms_key_name == original_kms_key_name

# Set a new encryption key to use for the destination.
# TODO: Replace this key with a key you have created in KMS.
updated_kms_key_name = (
    "projects/cloud-samples-tests/locations/us/keyRings/test/cryptoKeys/otherkey"
)
table.encryption_configuration = bigquery.EncryptionConfiguration(
    kms_key_name=updated_kms_key_name
)

table = client.update_table(table, ["encryption_configuration"])  # API request

assert table.encryption_configuration.kms_key_name == updated_kms_key_name
assert original_kms_key_name != updated_kms_key_name

设置数据集默认密钥

除非在创建表时指定了不同的 Cloud KMS 密钥,否则您可以设置数据集范围的默认 Cloud KMS 密钥,该密钥将应用到数据集中所有新创建的表。默认密钥不会应用到现有的表。如果更改默认密钥,则系统不会修改现有的任何表,并且更改后的密钥只会应用到更改默认密钥后创建的新表。

您可以通过以下方式应用、更改或移除数据集的默认密钥:

撤消 BigQuery 对 Cloud KMS 密钥的访问权限

您可以随时移除 BigQuery 对 Cloud KMS 密钥的访问权限,方法是撤消该密钥的 IAM 权限。

如果 BigQuery 失去对 Could KMS 密钥的访问权限,用户体验会受到严重影响,并且数据可能会丢失:

  • 这些受 CMEK 保护的表中的数据将不再能够访问;querycpextracttabledata.list 命令全部都将失败。

  • 新数据无法再添加到这些受 CMEK 保护的表中。

  • 重新授予访问权限后,对这些表所执行的查询的性能也可能在数天内都有所下降。

Cloud KMS 密钥轮替的影响

当与表关联的 Cloud KMS 密钥轮替时,BigQuery 不会自动轮替表加密密钥。现有表继续使用创建时所用的密钥版本。新表将使用当前的密钥版本。

限制

BigQuery 对 Cloud KMS 密钥的访问权限

如果符合以下条件,Cloud KMS 密钥会被视为可由 BigQuery 使用和访问:

  • 密钥已启用
  • BigQuery 服务帐号拥有该密钥的加密和解密权限

以下部分介绍当密钥不可访问时,对流式插入和长期无法访问的数据所造成的影响。

对流式插入的影响

在流式插入请求之后的 48 小时内,Cloud KMS 密钥必须至少连续 24 小时可用且可访问。如果不能使用和访问密钥,流式插入的数据可能会无法完整保留,并且可能会丢失。如需详细了解流式插入,请参阅将数据流式插入到 BigQuery 中

对长期无法访问的数据的影响

由于 BigQuery 提供托管式存储空间,因此长期无法访问的数据与 BigQuery 的架构是不兼容的。如果连续 60 天无法使用和访问特定 BigQuery 表的 Cloud KMS 密钥,BigQuery 可能会选择删除该表及其关联的数据。在删除数据之前,BigQuery 会提前至少 7 天向与结算帐号关联的电子邮件地址发送电子邮件。

使用表修饰器

如果您通过为 loadcpquery 操作使用 WRITE_TRUNCATE 这一写入处置方式来替换受 Cloud KMS 保护的表中的数据,则根据快照修饰器时间,您可能无法通过表修饰器查询该表。

假设表在时间 T 被替换并且快照修饰器 snapshot_time 对应的时间小于 T,下表显示了您是否可以对 snapshot_time 进行查询:

T 之前的加密类型 T 之后的加密类型 snapshot_time
Cloud KMS 加密 Cloud KMS 加密 无法查询
默认加密 Cloud KMS 加密 可以查询
Cloud KMS 加密 默认加密 无法查询

请注意,当使用范围修饰器时,类似的逻辑也适用于 <time2>

常见问题解答

谁需要 Cloud KMS 密钥的权限?

使用 CMEK 时,您无需重复指定权限。只要 BigQuery 服务帐号有权使用 Cloud KMS 密钥进行加密和解密,任何拥有 BigQuery 表权限的人都可以访问数据(即使无法直接访问 Cloud KMS 密钥)。

使用哪个服务帐号?

与表的 Cloud 项目关联的 BigQuery 服务帐号将用于解密该表的数据。BigQuery 服务帐号对每个项目都是唯一的。如果作业将数据写入受 Cloud KMS 保护的匿名表中,则将使用该作业的项目的服务帐号。

例如,假设有三个受 CMEK 保护的表:table1table2table3。如需在将 {project3.table3} 用作目标表的情况下查询 {project1.table1, project2.table2} 中的数据,请按以下指示操作:

  • 对于 project1.table1,使用 project1 服务帐号
  • 对于 project2.table2,使用 project2 服务帐号
  • 对于 project3.table3,使用 project3 服务帐号

BigQuery 可以通过哪些方式使用我的 Cloud KMS 密钥?

BigQuery 将使用 Cloud KMS 密钥解密数据来响应用户查询,例如 tabledata.listjobs.insert

BigQuery 还可以将密钥用于数据维护和存储优化任务,例如将数据转换为读取优化格式。

使用了哪些加密库?

BigQuery 依赖于 Cloud KMS 来实现 CMEK 功能。 Cloud KMS 使用 Tink 进行加密。

如何获取更多帮助?

如果您还有尚未得到解答的其他疑问,请参阅 BigQuery 支持Cloud KMS 支持

排查错误

下面描述了常见错误和建议的缓解措施。

错误 建议
请授予 Cloud KMS CryptoKey Encrypter/Decrypter 角色 与您项目关联的 BigQuery 服务帐号没有足够的 IAM 权限,无法对指定的 Cloud KMS 密钥执行操作。请按照错误消息或本文档中的说明操作,授予适当的 IAM 权限。
现有表加密设置与请求中指定的加密设置不匹配 如果目标表的加密设置与请求中的加密设置不匹配,则可能会发生这种情况。作为缓解措施,可使用 TRUNCATE 这一写入处置方式来替换该表,或指定其他目标表。
此区域不受支持 Cloud KMS 密钥的区域与目标表的 BigQuery 数据集的区域不匹配。作为缓解措施,可选择与数据集匹配的区域中的密钥,或加载与密钥区域匹配的数据集。