全部產品
Search
文件中心

Tair (Redis® OSS-Compatible):通過KMS受管理的執行個體密碼憑證

更新時間:Oct 30, 2024

當您的應用需要訪問Tair (Redis OSS-compatible)執行個體時,您可以將執行個體帳號口令儲存在KMS的憑據中(即執行個體憑據),業務應用通過整合阿里雲SDK、KMS SDK或憑據SDK向KMS動態擷取帳號口令。您還可以為憑據配置輪轉,以減少帳號口令的泄露風險。

功能介紹

在KMS託管Tair (Redis OSS-compatible)執行個體的帳號口令時,應用程式將無需配置待用資料庫帳號口令。管理員在KMS建立執行個體憑據,應用程式調用GetSecretValue介面擷取執行個體帳號和口令資訊,用於訪問執行個體。

例如您在KMS中自訂的執行個體憑證為username時,KMS最終會在執行個體中建立usernameusername_clone帳號,實現雙帳號託管,並使用該帳號訪問執行個體,相比較單帳號,雙帳號託管情境下應用程式的可用性、安全性更高。您可以在KMS控制台設定帳號輪轉策略,預設情況下KMS每24小時會進行帳號輪轉,即使用不同的帳號登入執行個體,提高安全性。更多資訊請參見Redis/Tair憑據

重要

請勿在Tair (Redis OSS-compatible)控制台中修改或刪除由KMS建立的帳號口令,以避免業務失敗。

使用限制

  • 不支援在控制台修改KMS建立的託管型帳號的密碼,您可以前往KMS控制台通過手動輪轉或配置自動輪轉策略更換該密碼,更多資訊請參見輪轉Redis/Tair憑據

  • 不支援在控制台刪除KMS建立的託管型帳號,如需刪除,請前往KMS控制台執行刪除操作,更多資訊請參見刪除Redis憑據

  • 不支援在控制台修改KMS建立的託管型帳號的備忘資訊。

前提條件

  • 已建立ECS執行個體,用於串連執行個體執行個體。本樣本ECS的作業系統為Alibaba Cloud Linux 3.2104 LTS 64位,同時已安裝JAVA 1.8.0。

  • 若使用RAM使用者(子帳號)或RAM角色管理執行個體憑據,您需要為該角色授予系統權限原則AliyunKMSSecretAdminAccess。具體操作請參見授權許可權

操作步驟

  1. 建立並啟用KMS。具體操作請參見建立和啟用KMS

    建立KMS時需選擇VPC,請選擇與ECS相同的VPC網路。

    若您已建立KMS,請在KMS中添加ECS的VPC網路,具體操作請參見配置VPC

  2. 建立應用存取點。具體操作請參見建立應用存取點

    建立後,瀏覽量會自動下載ClientKey資訊,包含應用身份憑證內容(ClientKeyContent,JSON檔案)憑證口令(ClientKeyPassword),請妥善儲存。

  3. 下載KMS的CA認證,您可以在KMS控制台的執行個體管理頁面進行下載,更多資訊請參見擷取KMS的CA認證

  4. 建立一個使用者主要金鑰。具體操作請參見密鑰管理快速入門

  5. 建立Tair (Redis OSS-compatible)執行個體憑據。具體操作請參見建立Redis/Tair憑據

  6. 編寫Java測試代碼。

    1. 在專案中添加Maven依賴,從Maven倉庫中自動下載Java安裝包。同時還可以將專案依賴打進作業JAR包,需增加下述<build>。

          <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(預設為600s),在緩衝周期內,會返回緩衝的密碼值,而不去訪問 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)); // 設定緩衝時間,防止頻繁請求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. 將整個專案打進JAR包,命令為mvn package

  7. 在ECS中,通過Java SDK串連執行個體。

    本樣本的文法如下:

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

    參數說明:

    • kms-redis-jar-with-dependencies.jar:JAR包,請使用尾碼為jar-with-dependencies的JAR包。

    • kmsEndpoint:KMS的VPC地址,您可以在KMS執行個體詳情頁擷取。

    • clientKeyFilePath:存取點應用身份憑證內容,為步驟2下載的JSON檔案。

    • clientKeyPass:存取點憑證口令,內容在步驟2下載的TXT檔案中。

    • caCertPath:KMS執行個體的CA認證,為步驟3下載的PEM檔案。

    • secretName:步驟5中建立執行個體憑據的名稱。

    • redisHost:執行個體的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控制台執行立即輪轉憑據測試,具體操作請參見輪轉Redis/Tair憑據

    輪轉表示KMS將使用另一個帳號(usernameusername_clone)訪問執行個體。

    此時,ECS的串連若仍正常,則表示當前可以實現密碼滾動功能。

    ...
    30
    OK
    31
    OK
    32
    OK
    33
    OK
  9. 在執行個體執行主從切換(HA)測試,觀察用戶端情況。

    預期返回如下,表示在HA時串連閃斷,KMS執行個體更新憑據並成功重新串連:

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

相關文檔