當您的應用需要訪問云數據庫 Tair(兼容 Redis)實例時,您可以將實例賬號口令存儲在KMS的憑據中(即實例憑據),業務應用通過集成阿里云SDK、KMS SDK或憑據SDK向KMS動態獲取賬號口令。您還可以為憑據配置輪轉,以減少賬號口令的泄露風險。
功能介紹
在KMS托管云數據庫 Tair(兼容 Redis)實例的賬號口令時,應用程序將無需配置靜態數據庫賬號口令。管理員在KMS創建實例憑據,應用程序調用GetSecretValue接口獲取實例賬號和口令信息,用于訪問實例。
例如您在KMS中自定義的實例憑證為username時,KMS最終會在實例中創建username、username_clone賬號,實現雙賬號托管,并使用該賬號訪問實例,相比較單賬號,雙賬號托管場景下應用程序的可用性、安全性更高。您可以在KMS控制臺設置賬號輪轉策略,默認情況下KMS每24小時會進行賬號輪轉,即使用不同的賬號登錄實例,提高安全性。更多信息請參見Redis/Tair憑據。
請勿在云數據庫 Tair(兼容 Redis)控制臺中修改或刪除由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。具體操作請參見授權權限。
操作步驟
創建并啟用KMS。具體操作請參見創建和啟用KMS。
創建KMS時需選擇VPC,請選擇與ECS相同的VPC網絡。
若您已創建KMS,請在KMS中添加ECS的VPC網絡,具體操作請參見配置VPC。
創建應用接入點。具體操作請參見創建應用接入點。
創建后,瀏覽量會自動下載ClientKey信息,包含應用身份憑證內容(ClientKeyContent,JSON文件)和憑證口令(ClientKeyPassword),請妥善保存。
下載KMS的CA證書,您可以在KMS控制臺的實例管理頁面進行下載,更多信息請參見獲取KMS的CA證書。
創建一個用戶主密鑰。具體操作請參見密鑰管理快速入門。
創建云數據庫 Tair(兼容 Redis)實例憑據。具體操作請參見創建Redis/Tair憑據。
編寫Java測試代碼。
在項目中添加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>
編寫主代碼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); } } } }
編寫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 } }
將整個項目打進JAR包,命令為
mvn package
。
在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
您可以在KMS控制臺執行立即輪轉憑據測試,具體操作請參見輪轉Redis/Tair憑據。
輪轉表示KMS將使用另一個賬號(username或username_clone)訪問實例。
此時,ECS的連接若仍正常,則表示當前可以實現密碼滾動功能。
... 30 OK 31 OK 32 OK 33 OK
在實例執行主從切換(HA)測試,觀察客戶端情況。
預期返回如下,表示在HA時連接閃斷,KMS實例更新憑據并成功重新連接:
138 OK 139 redis.clients.jedis.exceptions.JedisConnectionException: Unexpected end of stream. OK 142 OK 143 OK