使用 IAM 进行身份验证 - 亚马逊 ElastiCache

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

使用 IAM 进行身份验证

概述

使用IAM身份验证,当您的缓存配置为OSS使用 Valkey 或 Redis 版本 7 或更高OSS版本时,您可以使用 AWS IAM身份验证 ElastiCache 与 Valkey 或 Redis 的连接。这使您可以增强安全模型并简化许多管理安全任务。您还可以使用IAM身份验证为每个 ElastiCache 缓存和 ElastiCache 用户配置精细的访问控制,遵循最低权限权限原则。IAM ElastiCache 使用 Valkey 或 Redis 进行身份验证OSS的工作原理是,在 Valkey、Redis 或命令中提供短期IAM身份验证令牌,而不是 Valkey、Redis 或命令中的长期 ElastiCache 用户密码。OSS AUTH HELLO有关IAM身份验证令牌的更多信息,请参阅《 AWS 通用参考指南》中的签名版本 4 签名过程和下面的代码示例。

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

要 AWS IAM与一起使用 ElastiCache,首先需要创建一个身份验证模式设置为的 ElastiCache 用户IAM。然后,您可以创建或重复使用身IAM份。IAM身份需要关联的策略才能将elasticache:Connect操作授予 ElastiCache 缓存和 ElastiCache 用户。配置完成后,您可以使用IAM用户或角色的 AWS 凭据创建IAM身份验证令牌。最后,在连接到缓存时,您需要在 Valkey 或 Redis OSS 客户端中提供短期IAM身份验证令牌作为密码。支持凭证提供程序的 Valkey 或 Redis OSS 客户端可以自动为每个新连接生成临时证书。 ElastiCache 将对IAM启用该功能的 ElastiCache 用户的连接请求进行IAM身份验证,并将使用验证连接请求。IAM

限制

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

  • IAM ElastiCache 与 Valkey 7.2 或更高版本以及 Redis 7.0 或更高OSS版本一起使用时,可以使用身份验证。

  • 对于IAM已启用的 ElastiCache 用户,用户名和用户 ID 属性必须相同。

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

  • ElastiCache 与 Valkey 或 Redis 的IAM经过身份验证的连接OSS将在 12 小时后自动断开。通过发送带有新IAM身份验证令牌的AUTHHELLO命令,可以将连接延长 12 小时。

  • IAMMULTI EXEC命令中不支持身份验证。

  • 目前,IAM身份验证支持以下全局条件上下文密钥:

    • 对无服务器缓存使用IAM身份验证时,支持aws:VpcSourceIpaws:SourceVpcaws:SourceVpceaws:CurrentTimeaws:EpochTime、和aws:ResourceTag/%s(来自关联的无服务器缓存和用户)。

    • 对复制组使用IAM身份验证时,支持aws:SourceIpaws:ResourceTag/%s(来自关联的复制组和用户)。

    有关全局条件上下文键的更多信息,请参阅《IAM用户指南》中的AWS 全局条件上下文密钥

设置

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

  1. 创建缓存

    aws elasticache create-serverless-cache \ --serverless-cache-name cache-01 \ --description "ElastiCache IAM auth application" \ --engine redis
  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.json 的文件中。

    { "Version": "2012-10-17", "Statement": [ { "Effect" : "Allow", "Action" : [ "elasticache:Connect" ], "Resource" : [ "arn:aws:elasticache:us-east-1:123456789012:serverlesscache:cache-01", "arn:aws:elasticache:us-east-1:123456789012:user:iam-user-01" ] } ] }
  4. 创建IAM角色。

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

    aws iam create-policy \ --policy-name "elasticache-allow-all" \ --policy-document file://policy.json
  6. 将IAM策略附加到该角色。

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

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

    aws elasticache create-user-group \ --user-group-id iam-user-group-01 \ --engine redis \ --user-ids default iam-user-01 aws elasticache modify-serverless-cache \ --serverless-cache-name cache-01 \ --user-group-id iam-user-group-01

连接

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

您首先需要使用 AWS SigV4 预签名请求生成短期IAM身份验证令牌。之后,在连接到 Valkey 或 Redis OSS 缓存时,您需要提供IAM身份验证令牌作为密码,如下例所示。

String userId = "insert user id"; String cacheName = "insert cache name"; boolean isServerless = true; String region = "insert region"; // Create a default AWS Credentials provider. // This will look for AWS credentials defined in environment variables or system properties. AWSCredentialsProvider awsCredentialsProvider = new DefaultAWSCredentialsProviderChain(); // Create an IAM authentication token request and signed it using the AWS credentials. // The pre-signed request URL is used as an IAM authentication token for ElastiCache (Redis OSS). IAMAuthTokenRequest iamAuthTokenRequest = new IAMAuthTokenRequest(userId, cacheName, region, isServerless); String iamAuthToken = iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.getCredentials()); // Construct Redis OSS URL with IAM Auth credentials provider RedisURI redisURI = RedisURI.builder() .withHost(host) .withPort(port) .withSsl(ssl) .withAuthentication(userId, iamAuthToken) .build(); // Create a new Lettuce Redis OSS client RedisClient client = RedisClient.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 PARAM_RESOURCE_TYPE = "ResourceType"; private static final String RESOURCE_TYPE_SERVERLESS_CACHE = "ServerlessCache"; private static final String ACTION_NAME = "connect"; private static final String SERVICE_NAME = "elasticache"; private static final long TOKEN_EXPIRY_SECONDS = 900; private final String userId; private final String cacheName; private final String region; private final boolean isServerless; public IAMAuthTokenRequest(String userId, String cacheName, String region, boolean isServerless) { this.userId = userId; this.cacheName = cacheName; this.region = region; this.isServerless = isServerless; } 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(userId)); if (isServerless) { request.addParameters(PARAM_RESOURCE_TYPE, Collections.singletonList(RESOURCE_TYPE_SERVERLESS_CACHE)); } return request; } private URI getRequestUri() { return URI.create(String.format("%s%s/", REQUEST_PROTOCOL, cacheName)); } 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()); } }

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

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

String userId = "insert user id"; String cacheName = "insert cache name"; boolean isServerless = true; String region = "insert region"; // Create a default AWS Credentials provider. // This will look for AWS 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 ElastiCache (Redis OSS). IAMAuthTokenRequest iamAuthTokenRequest = new IAMAuthTokenRequest(userId, cacheName, region, isServerless); // Create a Redis OSS credentials provider using IAM credentials. RedisCredentialsProvider redisCredentialsProvider = new RedisIAMAuthCredentialsProvider( userId, iamAuthTokenRequest, awsCredentialsProvider); // Construct Redis OSS URL with IAM Auth credentials provider RedisURI redisURI = RedisURI.builder() .withHost(host) .withPort(port) .withSsl(ssl) .withAuthentication(redisCredentialsProvider) .build(); // Create a new Lettuce Redis OSS client RedisClient client = RedisClient.create(redisURI); client.connect();

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

public class RedisIAMAuthCredentialsProvider implements RedisCredentialsProvider { private static final long TOKEN_EXPIRY_SECONDS = 900; private final AWSCredentialsProvider awsCredentialsProvider; private final String userId; private final IAMAuthTokenRequest iamAuthTokenRequest; private final Supplier<String> iamAuthTokenSupplier; public RedisIAMAuthCredentialsProvider(String userId, 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(userId, iamAuthTokenSupplier.get())); } private String getIamAuthToken() { return iamAuthTokenRequest.toSignedRequestUri(awsCredentialsProvider.getCredentials()); } }