すべてのプロダクト
Search
ドキュメントセンター

Tair (Redis® OSS-Compatible):KMSを使用したTair (Redis OSS-compatible)シークレットの管理

最終更新日:Nov 27, 2024

アプリケーションがTair (Redis OSS-compatible)にアクセスする必要がある場合、ApsaraDB for Tairデータベースアカウントのパスワードを、Key Management Service (KMS) が管理するApsaraDB for Tairシークレットに保存できます。 ビジネスアプリケーションは、Alibaba Cloud SDK、KMS SDK、またはシークレットSDKを統合することにより、KMSからアカウントパスワードを動的に取得します。 シークレットローテーションを設定して、アカウントのパスワード漏洩のリスクを減らすことができます。

機能の説明

KMSでTair (Redis OSS-compatible)データベースアカウントを管理する場合、アプリケーションで静的データベースアカウントを構成する必要はありません。 KMSのApsaraDB for TairデータベースのアカウントにApsaraDB for Tairシークレットを作成した後、アプリケーションはGetSecretValue操作を呼び出してシークレットを取得し、ApsaraDB for Tairデータベースにアクセスできます。

たとえば、KMSで作成したApsaraDB For Tairシークレットの名前がusernameの場合、KMSはApsaraDB for Tairインスタンスにusernameアカウントとusername_cloneアカウントを作成し、そのアカウントを使用してApsaraDB for Tairインスタンスにアクセスします。 シングルアカウントモードと比較して、デュアルアカウントモードはアプリケーションの可用性とセキュリティを向上させます。 KMSコンソールでアカウントローテーションポリシーを設定できます。 デフォルトでは、KMSは24時間ごとにアカウントローテーションを実行します。 セキュリティを向上させるために、ApsaraDB for Tairインスタンスへのログインに異なるアカウントが使用されます。 詳細については、「Tair (Redis OSS-compatible)/Tairシークレットの管理と使用」をご参照ください。

重要

サービス障害を防ぐため、ApsaraDB for Tairのアカウントパスワードを変更または削除しないでください。

image

制限事項

  • ApsaraDB for TairコンソールでKMSによって作成された管理アカウントのパスワードを変更することはできません。 ただし、パスワードを手動でローテーションするか、KMSコンソールで自動ローテーションポリシーを設定することで、パスワードを変更できます。 詳細については、「Tair (Redis OSS-compatible)/Tairシークレットの回転」をご参照ください。

  • ApsaraDB for Tairコンソールでは、KMSによって作成されたマネージドアカウントは削除できません。 管理アカウントを削除するには、KMSコンソールに移動します。 詳細については、「Tair (Redis OSS-compatible)/Tairシークレットの削除」をご参照ください。

  • ApsaraDB for TairコンソールでKMSによって作成された管理アカウントの説明を変更することはできません。

前提条件

  • 管理するApsaraDB for Tairインスタンスに接続するためのElastic Compute Service (ECS) インスタンスが作成されます。 この例では、ECSインスタンスのオペレーティングシステムはAlibaba Cloud Linux 3.2104 LTS 64ビットで、ECSインスタンスにJava 1.8.0がインストールされています。

  • Resource Access Management (RAM) ユーザーまたはRAMロールを使用してTair (Redis OSS-compatible)シークレットを管理する場合は、AliyunKMSSecretAdminAccessシステムポリシーをロールにアタッチする必要があります。 詳細については、「RAMユーザーへの権限付与」をご参照ください。

手順

  1. KMSインスタンスを購入して有効にします。 詳細については、「KMSインスタンスの購入と有効化」をご参照ください。

    KMSインスタンスを作成するときは、ECSインスタンスと同じ仮想プライベートクラウド (VPC) を選択します。

    すでにKMSインスタンスを作成している場合は、ECSインスタンスのVPCをKMSインスタンスに追加します。 詳細については、「同じリージョンの複数のVPCからのKMSインスタンスへのアクセス」をご参照ください。

  2. アプリケーションアクセスポイント (AAP) を作成します。 詳細については、「AAPの作成」をご参照ください。

    AAPが作成されると、ブラウザは自動的にクライアントキー情報をダウンロードします。 クライアントキー情報は、クライアントキーの内容パスワードとを含む。 クライアントのキーコンテンツはJSONファイルに保存されます。 情報を秘密にしてください。

  3. KMSコンソールの [インスタンス] ページで、KMSインスタンスの認証局 (CA) 証明書をダウンロードします。 詳細については、「KMSインスタンスのCA証明書の取得」をご参照ください。

  4. カスタマーマスターキー (CMK) を作成します。 詳細については、「キーの使用開始方法」をご参照ください。

  5. Tair (Redis OSS-compatible)シークレットを作成します。 詳細については、「Tair (Redis OSS-compatible)/Tairシークレットの作成」をご参照ください。

  6. Javaテストコードを記述します。

    1. Mavenの依存関係をプロジェクトに追加します。 その後、JavaパッケージがMavenリポジトリから自動的にダウンロードされます。 次の <build> セクションを追加して、プロジェクトの依存関係を単一のJARファイルにパッケージ化することもできます。

          <dependencies>
              <dependency>
                  <groupId>redis.clients</groupId>
                  <artifactId>jedis</artifactId>
                  <version>5.1.0</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>alibabacloud-dkms-gcs-sdk</artifactId>
                  <version>0.5.2</version>
              </dependency>
              <dependency>
                  <groupId>com.aliyun</groupId>
                  <artifactId>tea</artifactId>
                  <version>1.2.3</version>
              </dependency>
              <dependency>
                  <groupId>org.slf4j</groupId>
                  <artifactId>slf4j-api</artifactId>
                  <version>1.7.10</version>
              </dependency>
              <dependency>
                  <groupId>ch.qos.logback</groupId>
                  <artifactId>logback-classic</artifactId>
                  <version>1.2.9</version>
              </dependency>
          </dependencies>
      
          <build>
              <plugins>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-assembly-plugin</artifactId>
                      <version>3.3.0</version>
                      <configuration>
                          <archive>
                              <manifest>
                                  <mainClass>
                                      com.aliyun.KMSJedisTest
                                  </mainClass>
                              </manifest>
                          </archive>
                          <descriptorRefs>
                              <descriptorRef>jar-with-dependencies</descriptorRef>
                          </descriptorRefs>
                      </configuration>
                      <executions>
                          <execution>
                              <id>assemble-all</id>
                              <phase>package</phase>
                              <goals>
                                  <goal>single</goal>
                              </goals>
                          </execution>
                      </executions>
                  </plugin>
                  <plugin>
                      <groupId>org.apache.maven.plugins</groupId>
                      <artifactId>maven-compiler-plugin</artifactId>
                      <configuration>
                          <source>1.8</source>
                          <target>1.8</target>
                      </configuration>
                  </plugin>
              </plugins>
          </build>
    2. KMSJedisTest.javaメインクラスを実装するコードを記述します。

      説明

      この例では、新しい接続が確立されるたびにシステムがKMSからパスワードを取得しないように、credentialCacheTimeパラメーターが追加されています。 credentialCacheTimeパラメーターのデフォルト値は600秒です。 キャッシュ時間中、KMSにアクセスせずにキャッシュされたパスワードが返されます。 setCredentialCacheTime操作を呼び出して、キャッシュ期間を調整できます。 キャッシュ期間を10分以上に設定することを推奨します。

      package com.aliyun;
      
      import java.time.Duration;
      
      import redis.clients.jedis.DefaultJedisClientConfig;
      import redis.clients.jedis.HostAndPort;
      import redis.clients.jedis.Jedis;
      import redis.clients.jedis.JedisPool;
      
      public class KMSJedisTest {
          public static void main(String[] args) throws Exception {
              if (args.length < 2) {
                  System.out.println(
                      "Please input kmsEndpoint, clientKeyFilePath, clientKeyPass, caCertPath, secretName, redisHost");
                  return;
              }
      
              String endpoint = args[0];
              String clientKeyFilePath = args[1];
              String clientKeyPass = args[2];
              String caCertPath = args[3];
              String secretName = args[4];
              KMSRedisCredentialsProvider kmsRedisCredentialsProvider = new KMSRedisCredentialsProvider(endpoint,
                  clientKeyFilePath, clientKeyPass, caCertPath, secretName);
              kmsRedisCredentialsProvider.setCredentialCacheTime(Duration.ofSeconds(10)); // Specify a cache duration to prevent frequent requests to KMS.
      
              String redisHost = args[5];
              JedisPool jedisPool = new JedisPool(HostAndPort.from(redisHost),
                  DefaultJedisClientConfig.builder().credentialsProvider(kmsRedisCredentialsProvider).build());
      
              for (int i = 0; i < Integer.MAX_VALUE; i++) {
                  Thread.sleep(1000);
                  try (Jedis jedis = jedisPool.getResource()) {
                      System.out.println(jedis.set("" + i, "" + i));
                      System.out.println(jedis.get("" + i));
                  } catch (Exception e) {
                      System.out.println(e);
                  }
              }
          }
      }
      
    3. KMSRedisCredentialsProvider.javaクラスを実装するコードを記述します。

      package com.aliyun;
      
      import java.time.Duration;
      import java.time.LocalDateTime;
      import java.time.format.DateTimeFormatter;
      
      import org.json.JSONObject;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;
      import redis.clients.jedis.DefaultRedisCredentials;
      import redis.clients.jedis.RedisCredentials;
      import redis.clients.jedis.RedisCredentialsProvider;
      
      import com.aliyun.dkms.gcs.openapi.models.Config;
      import com.aliyun.dkms.gcs.sdk.Client;
      import com.aliyun.dkms.gcs.sdk.models.*;
      
      public class KMSRedisCredentialsProvider implements RedisCredentialsProvider {
          private static final Logger logger = LoggerFactory.getLogger(KMSRedisCredentialsProvider.class);
      
          private final String endpoint;
          private final String clientKeyFilePath;
          private final String clientKeyPass;
          private final String caCertPath;
          private final String secretName;
          private static Client client = null;
      
          // credential cache time
          private Duration credentialCacheTime = Duration.ofSeconds(600);
          private DefaultRedisCredentials cachedCredentials = null;
          private LocalDateTime credentialsExpiration = null;
      
          public KMSRedisCredentialsProvider(String endpoint, String clientKeyFilePath, String clientKeyPass,
              String caCertPath, String secretName) {
              this.endpoint = endpoint;
              this.clientKeyFilePath = clientKeyFilePath;
              this.clientKeyPass = clientKeyPass;
              this.caCertPath = caCertPath;
              this.secretName = secretName;
              createClientInstance(endpoint, clientKeyFilePath, clientKeyPass, caCertPath);
          }
      
          public void setCredentialCacheTime(Duration credentialCacheTime) {
              this.credentialCacheTime = credentialCacheTime;
          }
      
          private static synchronized void createClientInstance(String endpoint, String clientKeyFilePath,
              String clientKeyPass, String caCertPath) {
              if (client == null) {
                  try {
                      client = new Client(new Config()
                          .setProtocol("https")
                          .setEndpoint(endpoint)
                          .setCaFilePath(caCertPath)
                          .setClientKeyFile(clientKeyFilePath)
                          .setPassword(clientKeyPass));
                  } catch (Exception e) {
                      logger.error("Init kms client failed", e);
                      throw new RuntimeException(e);
                  }
              }
          }
      
          @Override
          public RedisCredentials get() {
              try {
                  LocalDateTime now = LocalDateTime.now();
                  // Check cache
                  if (cachedCredentials != null && now.isBefore(credentialsExpiration)) {
                      return cachedCredentials;
                  }
      
                  GetSecretValueRequest request = new GetSecretValueRequest().setSecretName(secretName);
                  GetSecretValueResponse getSecretValueResponse = client.getSecretValue(request);
                  logger.debug("Now: " + now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +
                      ", getSecretValueRequest: " + request);
                  String secretData = getSecretValueResponse.getSecretData();
                  JSONObject secretObject = new JSONObject(secretData);
                  if (secretObject.get("AccountName") == null || secretObject.get("AccountPassword") == null) {
                      throw new IllegalArgumentException("secretData must contain AccountName and AccountPassword");
                  }
                  cachedCredentials = new DefaultRedisCredentials(secretObject.get("AccountName").toString(),
                      secretObject.get("AccountPassword").toString());
                  credentialsExpiration = now.plusSeconds(credentialCacheTime.getSeconds());
                  return cachedCredentials;
              } catch (Exception e) {
                  logger.error("get secret failed", e);
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void prepare() {
              // do nothing
          }
      
          @Override
          public void cleanUp() {
              // do nothing
          }
      }
      
    4. mvn packageコマンドを実行して、プロジェクト全体をJARファイルにパッケージ化します。

  7. ECSインスタンスで、SDK for Javaを使用してApsaraDB for Tairインスタンスに接続します。

    サンプル構文:

    java -jar <kms-redis-jar-with-dependencies.jar> <kmsEndpoint> <clientKeyFilePath> <clientKeyPass> <caCertPath> <secretName> <redisHost>

    パラメーター:

    • kms-redis-jar-with-dependencies.jar: Mavenパッケージプロセスから生成されたJARファイル。 必ず、JAR-with-dependenciesサフィックスを持つjarファイルを使用してください。

    • kmsEndpoint: KMSインスタンスのVPCエンドポイント。 KMSインスタンスの詳細ページでエンドポイントを取得できます。

    • clientKeyFilePath: クライアントキーファイルへのパス。 これは、ステップ2でダウンロードされたJSONファイルです。

    • clientKeyPass: クライアントキーのパスワード。 パスワードは、ステップ2でダウンロードしたTXTファイルに保存されます。

    • caCertPath: KMSインスタンスのCA証明書。手順3でダウンロードしたPEMファイルです。

    • secretName: 手順5で作成したApsaraDB for Tairシークレットの名前。

    • redisHost: ApsaraDB for TairインスタンスのVPCエンドポイントとポート番号 (r-bp1g727yrai5yh **** .redis.rds.aliyuncs.com:6379など) 。

    例:

    java -jar kms-redis-samples-1.0-SNAPSHOT-jar-with-dependencies.jar kst-hzz6674e7fbw21x9x****.cryptoservice.kms.aliyuncs.com /root/clientKey_KAAP.6432ddc6-f23a-4d78-ac84-****4598206b.json 267d1****1cda4415058e1d72ec49e0a /root/PrivateKmsCA_kst-hzz6674e7fbw21x9x****.pem kms-redis r-bp1g727yrai5yh****.redis.rds.aliyuncs.com:6379

    次の出力が返されます。これは、接続が確立されたことを示します。

    0
    OK
    1
    OK
    2
    OK
    3
    OK
    4
    OK
  8. KMSコンソールでシークレットを直ちに回転させます。 詳細については、「Tair (Redis OSS-compatible)/Tairシークレットの回転」をご参照ください。

    ローテーション中、KMSは別のアカウント (usernameまたはusername_clone) を使用してApsaraDB for Tairインスタンスにアクセスします。

    ECSインスタンスがTair (Redis OSS-compatible)インスタンスに接続できる場合、Tair (Redis OSS-compatible)パスワードローテーション機能は期待どおりに機能します。

    ...
    30
    OK
    31
    OK
    32
    OK
    33
    OK
  9. ApsaraDB for Tairインスタンスでマスタースレーブ切り替えまたは高可用性 (HA) 切り替えを実行し、クライアントのステータスを確認します。

    次の出力が返されます。これは、HA切り替え中に一時的な接続が発生し、KMSインスタンスがシークレットを更新してTair (Redis OSS-compatible)インスタンスに再接続することを示しています。

    138
    OK
    139
    redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream.
    OK
    142
    OK
    143
    OK

関連ドキュメント