教程:Memorystore for Redis 上的应用级加密

本教程介绍如何创建一个与 Cloud Key Management Service (Cloud KMS) API 通信的应用,以对存储在 Google Cloud 上 Memorystore for Redis 中的内容进行加密。如需详细了解本教程中使用的概念,请参阅与本教程关联的文档:应用级加密:Memorystore for Redis

本教程面向熟悉 Google Cloud、Linux、密钥管理服务、Redis、Git、Maven 和 Java 的应用开发者和安全专业人员。

本教程采用结构化方式,类似于测试驱动的开发方法。首先,您需要运行测试。然后引入更改,使测试失败。最后,您可以采取措施移除测试失败。

目标

  • 创建并测试一对 Memorystore for Redis 实例。
  • 转换数据并将其加载到 Redis 中。
  • 创建自己的密钥管理系统 (KMS)。
  • 了解加密密钥管理流程。

费用

本教程使用 Google Cloud 的以下收费组件:

您可使用价格计算器根据您的预计使用量来估算费用。 Google Cloud 新用户可能有资格申请免费试用

完成本教程后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

  1. 在 Google Cloud Console 的项目选择器页面上,选择或创建一个 Google Cloud 项目。

    转到“项目选择器”

  2. 确保您的 Cloud 项目已启用结算功能。 了解如何确认您的项目是否已启用结算功能

  3. 启用 Compute Engine, Cloud KMS, and Memorystore for Redis API。

    启用 API

  4. 在 Cloud Console 中,激活 Cloud Shell。

    激活 Cloud Shell

创建项目虚拟机实例

  1. 在 Cloud Shell 中,设置 gcloud 命令行工具配置:

    gcloud config set project PROJECT_NAME
    gcloud config set compute/zone us-central1-f
    gcloud config set compute/region us-central1
    

    PROJECT_NAME 替换为本教程的 Google Cloud 项目的名称。

  2. 创建虚拟机 (VM) 实例:

    gcloud compute instances create ale-instance \
        --machine-type=n1-standard-1 \
        --image-family=debian-10 \
        --image-project=debian-cloud \
        --boot-disk-size=200GB
    
  3. 在“虚拟机实例”页面的用户控制台中,点击 SSH 以登录虚拟机实例。

    Cloud Shell 中的 SSH 按钮。

    系统会打开一个新窗口,本教程称其为“项目窗口”(与“Cloud Shell 窗口”相对)。要完成本教程中的某些步骤,您需要在项目窗口和 Cloud Shell 窗口之间切换。

安装 Git 和 Maven

  • 在项目窗口中,安装 GitMaven

    sudo apt-get install git wget maven
    

    您可能会收到以下警告:

    E: Could not get lock /var/lib/dpkg/lock
    

    如果虚拟机启动脚本仍在运行,则可能会出现此错误。如果出现此错误,请等待一两分钟,然后重新运行上一个命令。

安装 Java 开发工具包 (JDK)

  1. 在项目窗口中,更改目录:

    cd /tmp
    
  2. 下载 Java 安装包:

    wget https://download.java.net/java/GA/jdk14.0.1/664493ef4a6946b186ff29eb326336a2/7/GPL/openjdk-14.0.1_linux-x64_bin.tar.gz
    

    输出内容类似如下:

    --2020-05-17 19:21:39--  https://download.java.net/java/GA/jdk14.0.1/664493ef4a6946b186ff29eb326336a2/7/GPL/openjdk-14.0.1_linux-x64_bin.tar.gz
    Resolving download.java.net (download.java.net)... 23.213.168.108
    Connecting to download.java.net (download.java.net)|23.213.168.108|:443... connected.
    HTTP request sent, awaiting response... 200 OK
    Length: 198665889 (189M) [application/x-gzip]
    Saving to: 'openjdk-14.0.1_linux-x64_bin.tar.gz'
    oenjdk-14.0.1_linux-x64_  80%[======================>      ] 151.72M  4.86MB/s    eta 8s
    
  3. 显示 Java 虚拟机 (JVM) 目录的内容:

    ls /usr/lib/jvm/
    

    您可能获得类似如下所示的输出:

    ls: /usr/lib/jvm/: No such file or directory
    

    此输出表明该文件夹不存在。如果是这样,请创建文件夹:

    sudo mkdir /usr/lib/jvm
    
  4. 安装 JDK:

    cd /usr/lib/jvm
    sudo tar xzf /tmp/openjdk-14.0.1_linux-x64_bin.tar.gz
    export JAVA_HOME=/usr/lib/jvm/jdk-14.0.1/
    

克隆 tinkCryptoHelper 代码库

在以下步骤中,您将克隆 tinkCryptoHelper 本教程使用的 Java 应用。

  1. 在项目窗口中,转到您的主目录:

    cd ~
    
  2. 克隆 tinkCryptoHelper 代码库:

    git clone https://github.com/google/tinkCryptoHelper.git
    cd tinkCryptoHelper
    

创建 tinkCryptoHelper 应用的构建

在以下步骤中,您可以使用 mvn 工具首次执行从 Java 源代码到可执行的 Java 软件包的 tinkCryptoHelper 构建过程。

  1. 在项目窗口中,构建软件包:

    mvn package
    

    输出结尾如下:

    [INFO] --------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] --------------------------------------------------------------------
    [INFO] Total time: 13.880 s
    [INFO] Finished at: 2020-05-17T19:29:13+00:00
    [INFO] Final Memory: 19M/116M
    [INFO] --------------------------------------------------------------------
    

测试系统

在以下部分中,您将运行测试以评估在更改配置后系统的行为。代码库中的代码包含 14 次集成测试。每项测试的行为各不相同,以确保系统正常运行。重要的是,测试依赖于配置。例如,如果您没有将系统数据库附加到系统,则测试不会尝试测试 Redis 连接。

基本功能测试

创建 tinkCryptoHelper 构建之后,可以测试其基本功能。您没有在线创建 Redis 数据库,因此连接测试不会失败。

  1. 在项目窗口中运行测试:

    mvn test
    

    输出内容类似如下:

    [INFO] -------------------------------------------------------
    [INFO]  T E S T S
    [INFO] -------------------------------------------------------
    [INFO] Running com.google.samples.kms.ale.AppTest
    AES256_GCM encryption cipherlength: 64
    Envelope encryption cipherlength: 224
    [INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.057 s - in com.google.samples.kms.ale.AppTest
    [INFO]
    [INFO] Results:
    [INFO]
    [INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0
    [INFO]
    [INFO] --------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] --------------------------------------------------------------------
    [INFO] Total time: 7.087 s
    [INFO] Finished at: 2020-05-17T19:34:21+00:00
    [INFO] Final Memory: 12M/56M
    

    关键结果为 Tests run: 14, Failures: 0, Errors: 0, Skipped: 0。此输出表明所有测试都已成功运行。

测试无实例的 Redis 连接

全集成测试的下一步要求应用 (tinkCryptoHelper) 与 Redis 通信。应用从 prefs.xml 文件获取 Redis 连接字符串信息。在上一部分中运行的第一个测试中,tinkCryptoHelper 应用根据 Java 源代码的类结构在不同位置创建了多个 prefs.xml 文件。

在练习中,您将 prefs.xml 文件中的 redisIsOnline 设置为 true。Redis 实例不在线,因为您仍需创建。此测试会导致连接测试失败。

  1. 在项目窗口中,列出源代码结构中的 XML 文件,以确保找到并修改正确的 prefs.xml 文件:

    find ~/.java -name '*.xml'
    

    输出内容类似如下:

    /home/USERNAME/.java/.userPrefs/com/google/samples/kms/ale/prefs.xml
    /home/USERNAME/.java/.userPrefs/com/google/samples/kms/prefs.xml
    /home/USERNAME/.java/.userPrefs/com/google/samples/prefs.xml
    /home/USERNAME/.java/.userPrefs/com/google/prefs.xml
    /home/USERNAME/.java/.userPrefs/com/prefs.xml
    

    在此输出中,USERNAME 是您登录时使用的用户名。

  2. 如需准备使用 Maven 测试 Redis,请使用 nano(或其他 UNIX 编辑器,例如 vi)打开以下文件:

    nano ~/.java/.userPrefs/com/google/samples/kms/ale/prefs.xml
    

    此配置文件的内容类似如下:

    <?xml version="1.0" encoding="UTF-8" standalone="no"?>
    <!DOCTYPE map SYSTEM "http://java.sun.com/dtd/preferences.dtd">
    <map MAP_XML_VERSION="1.0">
      <entry key="com.google.samples.kms.ale.AppTest" value="true"/>
    </map>
    
  3. 在配置文件的 map 部分中,再添加一个条目:

    <entry key="redisIsOnline" value="true"/>
    

    map 部分现在类似于以下内容:

    <map MAP_XML_VERSION="1.0">
      <entry key="com.google.samples.kms.ale.AppTest" value="true"/>
      <entry key="redisIsOnline" value="true"/>
    </map>
    
  4. 保存并退出该文件。

  5. 运行测试:

    mvn test
    

    由于 Redis 不在线,因此输出会显示如下错误:

    [ERROR]   AppTest.testRedis3_2:86 » JedisConnection Failed connecting to host 127.0.0.2:...
    [ERROR]   AppTest.testRedis4_0:79 » JedisConnection Failed connecting to host 127.0.0.1:...
    [ERROR]   AppTest.testRedisRoundtripRedis3_2:93->testRedisRoundtripRedisSeries:105->testRedisRoundtripClear:149 » JedisConnection
    [ERROR]   AppTest.testRedisRoundtripRedis4_0:99->testRedisRoundtripRedisSeries:105->testRedisRoundtripClear:149 » JedisConnection
    [INFO]
    

    您导致了失败的发生。在下一部分中,您将修正该故障。

创建 Redis 实例

在上一步中,测试失败,因为没有可供连接的 Redis 实例。如需更正此问题,您需要创建 Redis 实例。如需使用 tinkCryptoHelper 应用执行测试,您需要两个具有不同发布版本的 Redis 实例。测试在两个实例上运行。

创建 Redis 实例

  1. 在 Cloud Shell 中,创建两个不同版本的 Redis 实例:

    gcloud redis instances create redis32 --redis-version redis_3_2 --region us-central1
    gcloud redis instances create redis40 --redis-version redis_4_0 --region us-central1
    

    创建 Redis 3.2 和 Redis 4.0 实例需要几分钟时间。

  2. 查看实例相关信息:

    gcloud redis instances list --region us-central1
    

    输出内容类似如下:

    INSTANCE_NAME  VERSION    REGION       TIER   SIZE_GB  HOST            PORT  NETWORK  RESERVED_IP        STATUS  CREATE_TIME
    redis32        REDIS_3_2  us-central1  BASIC  1        10.126.141.179  6379  default  10.126.141.176/29  READY   2020-05-17T19:47:15
    redis40        REDIS_4_0  us-central1  BASIC  1        10.55.16.163    6379  default  10.55.16.160/29    READY   2020-05-17T19:50:44
    
  3. 从输出中复制 IP 地址。接下来的步骤中,您需要主机 IP 地址。

为 Redis 实例配置 IP 地址

下一步是告诉 tinkCryptoHelper 应用查找 Redis 实例的位置。

  1. 在项目窗口中,打开您之前打开的 prefs.xml 文件:

    nano ~/.java/.userPrefs/com/google/samples/kms/ale/prefs.xml
    
  2. 在配置文件的 map 部分中,在您之前添加的条目后面添加两行内容:

    <entry key="redisHost3_2" value="IP_ADDRESS3.2" />
    <entry key="redisHost4_0" value="IP_ADDRESS4.0" />
    

    请替换以下内容:

    • IP_ADDRESS3.2:您之前复制的 redis32 实例的值
    • IP_ADDRESS4.0:您之前复制的 redis40 实例的值
  3. 保存并退出该文件。

  4. 运行测试:

    mvn test
    

    输出内容类似如下:

    Set of 5000 cleartext values took on average 2µs on host 10.126.141.179
    Get of 5000 cleartext values took on average 1µs on host 10.126.141.179
    Set of 5000 encrypted values took on average 40µs on host 10.126.141.179
    ...
    Envelope encryption cipherlength: 224
    [INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.312 s - in com.google.samples.kms.ale.AppTest
    [INFO]
    [INFO] Results:
    [INFO]
    [INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0
    [INFO]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] ------------------------------------------------------------------------
    [INFO] Total time: 12.470 s
    [INFO] Finished at: 2020-05-17T20:55:46+00:00
    [INFO] Final Memory: 12M/56M
    [INFO] ------------------------------------------------------------------------
    

您已使用 Redis 成功完成集成测试。

转换数据并将其加载到 Redis

在上一部分中,测试有几个副作用,其中之一是向 Redis 实例插入数据。您必须完成此过程才能完成集成测试。如需使用您自己的数据测试系统,以下步骤演示了如何使用 tinkCryptoHelper 加密数据,然后使用您自己的数据更新 Redis 数据库。Git 代码库中提供了一个示例 data.csv 文件,但您可以创建自己的文件。

  1. 在项目窗口中,创建一个 JAR 文件:

    mvn assembly:single
    
  2. data.csv 文件转换为兼容 Redis import 命令的文件:

    $JAVA_HOME/bin/java -jar target/cryptoHelper-1.0-jar-with-dependencies.jar data.csv redis_import.txt
    

    redis_import.txt 文件包含一系列命令,可用于将数据插入 Redis。

  3. 安装 Redis 工具,其中包含命令行界面 (CLI):

    sudo apt-get install redis-tools
    
  4. 如果您不想保留 Redis 数据库中的现有数据,可执行以下操作:

    1. 使用 Redis CLI 连接到 Redis:

      redis-cli -h IP_ADDRESS
      

      IP_ADDRESS 替换为您之前复制的其中一个 IP 地址。

    2. 删除数据库中的数据:

      FLUSHALL
      exit
      
  5. 加载数据:

    cat redis_import.txt | redis-cli -h IPADDRESS --pipe
    
  6. 如需测试数据是否存在,请再次连接到 Redis 并获取密钥:

    1. 连接到 Redis:

      redis-cli -h IP_ADDRESS
      
    2. 获取密钥:

      get 1
      

      输出如下所示:

      Hello World
      

      如果您使用的是自己的数据,密钥和值很可能不同。

创建自己的 KMS

本教程中的 KMS 加密密钥属于 Google 所有的名为 tink-test-infrastructure 的 Cloud 项目,而不属于您创建的项目。此示例项目有一个 KMS 用于公开使用的加密密钥。

如需创建自己的 KMS 实例和密钥,您可以创建一个服务帐号并将该帐号与 Cloud KMS CryptoKey Encrypter/Decrypter IAM 角色关联 (roles/cloudkms.cryptoKeyEncrypterDecrypter)。使用此角色,服务帐号可以创建 KMS 密钥环并添加密钥。

设置服务帐号

  1. 在 Cloud Shell 中,创建一个新的服务帐号

    gcloud iam service-accounts create tink-509 \
        --description="Account for KMS" \
        --display-name="tinkAccount"
    
  2. 在 Cloud Console 中,转到 IAM 和管理页面。

    转到“IAM 和管理”页面

  3. 点击添加,然后将服务帐号 tink-509 添加到 Cloud KMS CryptoKey Encrypter/Decrypter 角色。

    在 Cloud Console 中添加 IAM 角色。

  4. 保存更改并退出 Cloud Console。

  5. 在项目窗口中,保存新服务帐号的 JSON 凭据密钥文件:

    1. 备份旧凭据密钥文件:

      mv kmsServiceAccountCredentials.json kmsServiceAccountCredentials.json.old
      
    2. 确保您已登录。运行以下命令,然后按照 OAuth 说明操作:

      gcloud config set project PROJECT_ID
      gcloud auth login
      

      PROJECT_ID 替换为您的 Cloud 项目 ID。

    3. 创建新的凭据密钥文件:

      gcloud iam service-accounts keys create kmsServiceAccountCredentials.json \
          --iam-account tink-509@app-lev-enc.iam.gserviceaccount.com
      

      输出内容类似如下:

      created key [c09dfea3892d6c309333f1998caf35845dc50608] of type [json] as [kmsServiceAccountCredentials.json] for [tink-509@app-lev-enc.iam.gserviceaccount.com]
      

创建 Cloud KMS 密钥环和密钥

  1. 在 Cloud Shell 中,创建一个名为 的 Cloud KMS 密钥环:

    gcloud kms keyrings create "unit-and-integration-testing" --location "global"
    gcloud kms keys create aead-key --purpose=encryption --location "global" \
        --keyring unit-and-integration-testing
    
  2. roles/cloudkms.cryptoKeyEncrypterDecrypter IAM 角色添加到 IAM 政策:

    gcloud kms keys add-iam-policy-binding \
        aead-key --location global --keyring unit-and-integration-testing \
        --member serviceAccount:tink-509@app-lev-enc.iam.gserviceaccount.com \
        --role roles/cloudkms.cryptoKeyEncrypterDecrypter
    

    此角色可让您的服务帐号访问密钥。

清除之前的 Redis 数据

之前的测试中,Redis 数据库填充了示例数据,但加密基于较旧的密钥。您需要清除数据并删除旧密钥。

  1. 在 Cloud Shell 中,列出所有 Redis 实例:

    gcloud redis instances list --region us-central1
    

    记下您所创建实例的 IP 地址。

  2. 连接到两个 Redis 实例:

    redis-cli -h IP_ADDRESS
    

    运行前述命令两次,并将 IP_ADDRESS 替换为每个实例的 IP 地址。

  3. 删除数据库中的数据:

    FLUSHALL
    exit
    

    当您在本教程后面部分运行最终测试时,Redis 表中的数据是使用新密钥加密的。

指定 tinkCryptoHelper 查找新 KMS 的位置

  1. 在项目窗口中,使用 UNIX 编辑器打开 KMS pref.xml 文件:

    nano ~/.java/.userPrefs/com/google/samples/kms/prefs.xml
    
  2. 将以下行添加为第二个条目:

    <entry key="keyResourceIdUri" value="gcp-kms://projects/PROJECT_ID/locations/global/keyRings/unit-and-integration-testing/cryptoKeys/aead-key"/>
    

    PROJECT_ID 替换为您的 Cloud 项目 ID。

运行最终测试

  • 在项目窗口中运行测试:

    mvn test
    

    输出内容类似如下:

    ...
    Envelope encryption cipherlength: 224
    [INFO] Tests run: 14, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 8.312 s - in com.google.samples.kms.ale.AppTest
    ...
    

    您已成功创建 KMS 和密钥,然后使用它们测试了 tinkCryptoHelper。

清理

为避免系统因本教程中使用的资源向您的 Google Cloud 帐号收取费用,您可以删除该项目。

删除项目

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤

  • 探索有关 Google Cloud 的参考架构、图表、教程和最佳做法。查看我们的云架构中心