Tair (Redis OSS-compatible) インスタンスの認証情報を KMS に保存することで、アプリケーションは静的認証情報を埋め込む代わりに、SDK を通じてパスワードを動的に取得できます。また、認証情報のローテーションを有効にすることで、漏洩リスクを軽減できます。
仕組み
KMS で管理される Tair (Redis OSS-compatible) インスタンスの認証情報を使用すると、アプリケーションに静的パスワードを保持する必要がなくなります。管理者は KMS でインスタンスの認証情報を作成し、アプリケーションは実行時に GetSecretValue オペレーションを呼び出してアカウントとパスワードを取得します。
例えば、認証情報の名前が username の場合、KMS はインスタンス上に username と username_clone の両方のアカウントを作成します。この二重アカウント方式により、より高い可用性とセキュリティを実現します。KMS コンソールでローテーションポリシーを設定できます。デフォルトでは、KMS は 24 時間ごとにアカウントをローテーションし、アクティブなアカウントを切り替えます。詳細については、「Redis/Tair の認証情報」をご参照ください。
サービスの中断を防ぐため、KMS で管理されているアカウントのパスワードを Tair (Redis OSS-compatible) コンソールで変更または削除しないでください。
制限事項
-
KMS で管理されているアカウントのパスワードを Tair コンソールで変更することはできません。手動でローテーションするか、KMS コンソールで自動ローテーションを設定してください。詳細については、「Redis/Tair の認証情報のローテーション」をご参照ください。
-
KMS で管理されているアカウントを Tair コンソールで削除することはできません。代わりに KMS コンソールで削除してください。詳細については、「Redis の認証情報の削除」をご参照ください。
-
KMS で管理されているアカウントの説明を Tair コンソールで変更することはできません。
前提条件
-
Tair インスタンスに接続できる ECS インスタンスが必要です。本ドキュメントでは、Java 1.8.0 を搭載した Alibaba Cloud Linux 3.2104 LTS 64 ビットを使用しています。
-
RAM ユーザーまたは RAM ロールを使用してインスタンスの認証情報を管理する場合は、[AliyunKMSSecretAdminAccess] システムポリシーをそのユーザーまたはロールにアタッチしてください。詳細については、「アクセス許可の付与」をご参照ください。
操作手順
-
KMS インスタンスを作成して有効化します。詳細については、「KMS インスタンスの作成と有効化」をご参照ください。
KMS インスタンスの作成時には、ECS インスタンスと同じ VPC を選択してください。
すでに KMS インスタンスをお持ちの場合は、ECS インスタンスの VPC をそれに追加してください。詳細については、「VPC の設定」をご参照ください。
-
アプリケーションアクセスポイント (AAP) を作成します。詳細については、「アプリケーションアクセスポイントの作成」をご参照ください。
作成後、ブラウザーから [アプリケーション ID 認証情報コンテンツ (ClientKeyContent、JSON ファイル)] と [認証情報セキュリティトークン (ClientKeyPassword)] がダウンロードされます。これらを安全に保管してください。
-
[Instance Management] ページで KMS インスタンスの CA 証明書をダウンロードします。詳細については、「KMS インスタンスの CA 証明書の取得」をご参照ください。
-
[カスタマーマスターキー (CMK)] を作成します。詳細については、「キー管理の開始」をご参照ください。
-
Tair (Redis OSS-compatible) インスタンスの認証情報を作成します。詳細については、「Redis/Tair の認証情報の作成」をご参照ください。
-
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> -
KMSJedisTest.java にメインコードを記述します。
説明この例では、認証情報を 10 秒間キャッシュします (setCredentialCacheTime で設定可能)。これにより、新しい接続のたびに KMS を呼び出すことを回避します。本番環境では、KMS への頻繁な呼び出しを避けるため、少なくとも 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( "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; // 認証情報のキャッシュ時間 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("KMS クライアントの初期化に失敗しました", e); throw new RuntimeException(e); } } } @Override public RedisCredentials get() { try { LocalDateTime now = LocalDateTime.now(); // キャッシュを確認 if (cachedCredentials != null && now.isBefore(credentialsExpiration)) { return cachedCredentials; } GetSecretValueRequest request = new GetSecretValueRequest().setSecretName(secretName); GetSecretValueResponse getSecretValueResponse = client.getSecretValue(request); logger.debug("現在時刻: " + 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 には AccountName と AccountPassword を含める必要があります"); } cachedCredentials = new DefaultRedisCredentials(secretObject.get("AccountName").toString(), secretObject.get("AccountPassword").toString()); credentialsExpiration = now.plusSeconds(credentialCacheTime.getSeconds()); return cachedCredentials; } catch (Exception e) { logger.error("シークレットの取得に失敗しました", e); throw new RuntimeException(e); } } @Override public void prepare() { // 何もしない } @Override public void cleanUp() { // 何もしない } } -
次のコマンドを実行して、プロジェクト全体を JAR ファイルにパッケージ化します:
mvn package
-
-
ECS インスタンス上で JAR を実行して Tair に接続します。
構文:
java -jar <kms-redis-jar-with-dependencies.jar> <kmsEndpoint> <clientKeyFilePath> <clientKeyPass> <caCertPath> <secretName> <redisHost>パラメーター:
-
kms-redis-jar-with-dependencies.jar: すべての依存関係を含むパッケージ化された JAR ファイルです。
-
kmsEndpoint: KMS インスタンスの VPC エンドポイントです。インスタンスの詳細ページで確認できます。
-
clientKeyFilePath: 手順 2 でダウンロードした AAP ID 認証情報 JSON ファイルです。
-
clientKeyPass: 手順 2 でダウンロードした認証情報セキュリティトークン (TXT ファイル) です。
-
caCertPath: 手順 3 でダウンロードした KMS インスタンスの CA 証明書 (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 の認証情報のローテーション」をご参照ください。
ローテーションにより、アクティブなアカウントが 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