使用 IAM 进行身份验证 - 适用于 Redis 的 Amazon MemoryDB
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

使用 IAM 进行身份验证

概述

使用 IAM 身份验证,您可以在您的集群配置为使用 Redis 版本 7 或更高版本时,使用Amazon IAM 身份对与 MemoryDB 的连接进行验证。这使您可以增强安全模型并简化许多管理安全任务。通过 IAM 身份验证,您可以为每个单独的 MemoryDB 集群和 MemoryDB 用户配置精细的访问控制,并遵循最低权限权限原则。MemoryDB Redis 的 IAM 身份验证的工作原理是在 Redis AUTHHELLO 命令中提供有效期很短的 IAM 身份验证令牌,而不是有效期很长的 MemoryDB 用户密码。有关 IAM 身份验证令牌的更多信息,请参阅《Amazon 一般参考指南》中的 Signature Version 4 签名流程和下面的代码示例。

您可以使用 IAM 身份及其关联策略进一步限制 Redis 访问权限。您还可以直接向来自联合身份提供商的用户授予对 MemoryDB 集群的访问权限。

要将 Amazon IAM 与 MemoryDB 一起使用,您首先需要创建身份验证模式设置为 IAM 的 MemoryDB 用户,然后创建或重用 IAM 身份。IAM 身份需要关联策略才能向 MemoryDB 集群和 MemoryDB 用户授予 memorydb:Connect 操作权限。配置完成后,您可以使用 IAM 用户或角色的 Amazon 凭证创建 IAM 身份验证令牌。最后,在连接到 MemoryDB 集群节点时,您需要在 Redis 客户端中提供有效期较短的 IAM 身份验证令牌作为密码。支持凭证提供程序的 Redis 客户端可以为每个新连接自动生成临时凭证。MemoryDB 将对启用 IAM 的 MemoryDB 用户的连接请求执行 IAM 身份验证,并将通过 IAM 验证连接请求。

限制

使用 IAM 身份验证时,以下限制适用:

  • 使用 Redis 引擎版本 7.0 或更高版本时,IAM 身份验证可用。

  • IAM 身份验证令牌的有效期为 15 分钟。对于长时间的连接,建议使用支持凭证提供程序接口的 Redis 客户端。

  • 经过 IAM 身份验证的 MemoryDB 连接将在 12 小时后自动断开。通过使用新 IAM 身份验证令牌发送 AUTHHELLO 命令,可以将连接延长 12 小时。

  • MULTI EXEC 命令不支持 IAM 身份验证。

  • 目前,IAM 身份验证并不支持所有的全局条件上下文键。有关全局条件上下文键的更多信息,请参阅《IAM 用户指南》中的 Amazon 全局条件上下文键

设置

要设置 IAM 身份验证,请执行以下操作:

  1. 创建集群

    aws memorydb create-cluster \ --cluster-name cluster-01 \ --description "MemoryDB IAM auth application" --node-type db.r6g.large \ --engine-version 7.0 \ --acl-name open-access
  2. 为您的角色创建 IAM 信任政策文档,如下所示,允许您的账户承担新角色。将策略保存到名为 trust-policy.json 的文件中。

    { "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, "Action": "sts:AssumeRole" } }
  3. 创建 IAM policy 文档,如下所示。将策略保存到名为 policy.json 的文件中。

    { "Version": "2012-10-17", "Statement": [ { "Effect" : "Allow", "Action" : [ "memorydb:connect" ], "Resource" : [ "arn:aws:memorydb:us-east-1:123456789012:cluster/cluster-01", "arn:aws:memorydb:us-east-1:123456789012:user/iam-user-01" ] } ] }
  4. 创建一个 IAM 角色。

    aws iam create-role \ --role-name "memorydb-iam-auth-app" \ --assume-role-policy-document file://trust-policy.json
  5. 创建 IAM policy。

    aws iam create-policy \ --policy-name "memorydb-allow-all" \ --policy-document file://policy.json
  6. 向角色附加 IAM policy。

    aws iam attach-role-policy \ --role-name "memorydb-iam-auth-app" \ --policy-arn "arn:aws:iam::123456789012:policy/memorydb-allow-all"
  7. 创建启用 IAM 的新用户。

    aws memorydb create-user \ --user-name iam-user-01 \ --authentication-mode Type=iam \ --access-string "on ~* +@all"
  8. 创建 ACL 并附加用户。

    aws memorydb create-acl \ --acl-name iam-acl-01 \ --user-names iam-user-01 aws memorydb update-cluster \ --cluster-name cluster-01 \ --acl-name iam-acl-01

连接

使用令牌作为密码进行连接

您首先需要使用 Amazon SigV4 预签名请求生成有效期较短的 IAM 身份验证令牌。之后,您需要在连接到 MemoryDB 集群时提供 IAM 身份验证令牌作为密码,如下例所示。

String userName = "insert user name" String clusterName = "insert cluster name" String region = "insert region" // Create a default Amazon Credentials provider. // This will look for Amazon credentials defined in environment variables or system properties. AWSCredentialsProvider awsCredentialsProvider = new DefaultAWSCredentialsProviderChain(); // Create an IAM authentication token request and signed it using the Amazon credentials. // The pre-signed request URL is used as an IAM authentication token for MemoryDB Redis. IAMAuthTokenRequest iamAuthTokenRequest = new IAMAuthTokenRequest(userName, clusterName, region); String iamAuthToken = iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.getCredentials()); // Construct Redis URL with IAM Auth credentials provider RedisURI redisURI = RedisURI.builder() .withHost(host) .withPort(port) .withSsl(ssl) .withAuthentication(userName, iamAuthToken) .build(); // Create a new Lettuce Redis client RedisClusterClient client = RedisClusterClient.create(redisURI); client.connect();

以下为 IAMAuthTokenRequest 的定义。

public class IAMAuthTokenRequest { private static final HttpMethodName REQUEST_METHOD = HttpMethodName.GET; private static final String REQUEST_PROTOCOL = "http://"; private static final String PARAM_ACTION = "Action"; private static final String PARAM_USER = "User"; private static final String ACTION_NAME = "connect"; private static final String SERVICE_NAME = "memorydb"; private static final long TOKEN_EXPIRY_SECONDS = 900; private final String userName; private final String clusterName; private final String region; public IAMAuthTokenRequest(String userName, String clusterName, String region) { this.userName = userName; this.clusterName = clusterName; this.region = region; } public String toSignedRequestUri(AWSCredentials credentials) throws URISyntaxException { Request<Void> request = getSignableRequest(); sign(request, credentials); return new URIBuilder(request.getEndpoint()) .addParameters(toNamedValuePair(request.getParameters())) .build() .toString() .replace(REQUEST_PROTOCOL, ""); } private <T> Request<T> getSignableRequest() { Request<T> request = new DefaultRequest<>(SERVICE_NAME); request.setHttpMethod(REQUEST_METHOD); request.setEndpoint(getRequestUri()); request.addParameters(PARAM_ACTION, Collections.singletonList(ACTION_NAME)); request.addParameters(PARAM_USER, Collections.singletonList(userName)); return request; } private URI getRequestUri() { return URI.create(String.format("%s%s/", REQUEST_PROTOCOL, clusterName)); } private <T> void sign(SignableRequest<T> request, AWSCredentials credentials) { AWS4Signer signer = new AWS4Signer(); signer.setRegionName(region); signer.setServiceName(SERVICE_NAME); DateTime dateTime = DateTime.now(); dateTime = dateTime.plus(Duration.standardSeconds(TOKEN_EXPIRY_SECONDS)); signer.presignRequest(request, credentials, dateTime.toDate()); } private static List<NameValuePair> toNamedValuePair(Map<String, List<String>> in) { return in.entrySet().stream() .map(e -> new BasicNameValuePair(e.getKey(), e.getValue().get(0))) .collect(Collectors.toList()); } }

使用凭证提供程序进行连接

以下代码显示了如何使用 IAM 身份验证凭证提供程序通过 MemoryDB 进行身份验证。

String userName = "insert user name" String clusterName = "insert cluster name" String region = "insert region" // Create a default Amazon Credentials provider. // This will look for Amazon credentials defined in environment variables or system properties. AWSCredentialsProvider awsCredentialsProvider = new DefaultAWSCredentialsProviderChain(); // Create an IAM authentication token request. Once this request is signed it can be used as an // IAM authentication token for MemoryDB Redis. IAMAuthTokenRequest iamAuthTokenRequest = new IAMAuthTokenRequest(userName, clusterName, region); // Create a Redis credentials provider using IAM credentials. RedisCredentialsProvider redisCredentialsProvider = new RedisIAMAuthCredentialsProvider( userName, iamAuthTokenRequest, awsCredentialsProvider); // Construct Redis URL with IAM Auth credentials provider RedisURI redisURI = RedisURI.builder() .withHost(host) .withPort(port) .withSsl(ssl) .withAuthentication(redisCredentialsProvider) .build(); // Create a new Lettuce Redis cluster client RedisClusterClient client = RedisClusterClient.create(redisURI); client.connect();

以下是 Lettuce Redis 集群客户端的示例,该客户端将 IAMAuthTokenRequest 封装在凭证提供程序中,以便在需要时自动生成临时凭证。

public class RedisIAMAuthCredentialsProvider implements RedisCredentialsProvider { private static final long TOKEN_EXPIRY_SECONDS = 900; private final AWSCredentialsProvider awsCredentialsProvider; private final String userName; private final IAMAuthTokenRequest iamAuthTokenRequest; private final Supplier<String> iamAuthTokenSupplier; public RedisIAMAuthCredentialsProvider(String userName, IAMAuthTokenRequest iamAuthTokenRequest, AWSCredentialsProvider awsCredentialsProvider) { this.userName = userName; this.awsCredentialsProvider = awsCredentialsProvider; this.iamAuthTokenRequest = iamAuthTokenRequest; this.iamAuthTokenSupplier = Suppliers.memoizeWithExpiration(this::getIamAuthToken, TOKEN_EXPIRY_SECONDS, TimeUnit.SECONDS); } @Override public Mono<RedisCredentials> resolveCredentials() { return Mono.just(RedisCredentials.just(userName, iamAuthTokenSupplier.get())); } private String getIamAuthToken() { return iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.getCredentials()); }