当您的应用需要访问云数据库 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