

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 以 IAM 進行身分驗證
<a name="auth-iam"></a>

**Topics**
+ [概觀](#auth-iam-overview)
+ [限制](#auth-iam-limits)
+ [設定](#auth-iam-setup)
+ [連接](#auth-iam-Connecting)

## 概觀
<a name="auth-iam-overview"></a>

透過 IAM 身分驗證，當您的快取設定為使用 Valkey 或 Redis OSS 第 7 版或更新版本時，您可以使用 AWS IAM 身分驗證 ElastiCache for Valkey 或 Redis OSS 的連線。這可讓您強化安全模型，並簡化許多管理安全任務。您也可以使用 IAM 身分驗證為個別 ElastiCache 快取和 ElastiCache 使用者設定精細的存取控制，並遵循最低權限許可原則。ElastiCache 的 IAM 身分驗證的運作方式是在 Valkey 或 Redis OSS `AUTH`或 `HELLO`命令中提供短期 IAM 身分驗證字符，而非長期 ElastiCache 使用者密碼。如需 IAM 身分驗證字符的詳細資訊，請參閱《 AWS 一般參考指南》中的 [Signature 第 4 版簽署程序](https://docs.aws.amazon.com//general/latest/gr/signature-version-4.html)，以及下面的程式碼範例。

您可以使用 IAM 身分及其相關政策來進一步限制 Valkey 或 Redis OSS 存取。您也可以將使用者從其聯合身分提供者的存取權直接授予 Valkey 或 Redis OSS 快取。

若要搭配 ElastiCache 使用 AWS IAM，您必須先建立身分驗證模式設定為 IAM 的 ElastiCache 使用者。然後，您可以建立或重複使用 IAM 身分。IAM 身分需有相關政策，才能將 `elasticache:Connect` 動作授予 ElastiCache 快取和使用者。設定完成後，您可以使用 IAM 使用者或角色的 AWS 登入資料建立 IAM 身分驗證字符。最後，您需要在連線至快取時，在 Valkey 或 Redis OSS 用戶端中提供短期 IAM 身分驗證字符做為密碼。支援登入資料的 Valkey 或 Redis OSS 用戶端可以為每個新連線自動產生臨時登入資料。ElastiCache 將為啟用 IAM 之 ElastiCache 使用者的連線請求執行 IAM 身分驗證，並將使用 IAM 驗證連線請求。

## 限制
<a name="auth-iam-limits"></a>

使用 IAM 身分驗證，會套用以下限制：
+ 使用 ElastiCache for Valkey 7.2 及更高版本或 Redis OSS 7.0 及更高版本時，可使用 IAM 身分驗證。
+ IAM 身分驗證需要在快取上啟用傳輸中加密 (TLS)。如需詳細資訊，請參閱[ElastiCache 傳輸中加密 (TLS)](in-transit-encryption.md)。
+ 針對已啟用 IAM 的 ElastiCache 使用者，使用者名稱和使用者 ID 屬性必須相同。
+ IAM 身分驗證字符的有效期限為 15 分鐘。對於長期連線，我們建議您使用支援登入資料提供者介面的 Valkey 或 Redis OSS 用戶端。
+ IAM 驗證的 ElastiCache for Valkey 或 Redis OSS 連線會在 12 小時後自動中斷連線。可以傳送包含新 IAM 身分驗證字符的 `AUTH` 或 `HELLO` 命令，將連線再延長 12 小時。
+ `MULTI`/`EXEC` 區塊內不支援 IAM 身分驗證。
+ 目前，IAM 身分驗證支援下列全域條件內容金鑰：
  + 使用 IAM 身分驗證搭配無伺服器快取時，可支援 `aws:VpcSourceIp`、`aws:SourceVpc`、`aws:SourceVpce`、`aws:CurrentTime`、`aws:EpochTime` 和 `aws:ResourceTag/%s` (來自相關聯的無伺服器快取和使用者)。
  + 使用 IAM 身分驗證搭配複寫群組時，可支援 `aws:SourceIp` 和 `aws:ResourceTag/%s` (來自相關聯的複寫群組和使用者)。

  如需有關全域條件內容索引鍵的詳細資訊，請參閱《IAM 使用者指南》中的 [AWS 全域條件內容索引鍵](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html)。

**注意**  
在快取建立時間，快取名稱會轉換為小寫。確保驗證程式碼以小寫形式提供快取名稱，以避免身分驗證錯誤。

## 設定
<a name="auth-iam-setup"></a>

設定 IAM 身分驗證：

1. 建立快取

   ```
   aws elasticache create-serverless-cache \
     --serverless-cache-name cache-01  \
     --description "ElastiCache IAM auth application" \
     --engine redis
   ```

1. 為您的角色建立如下所示的 IAM 信任政策文件，讓您的帳戶擔任新角色。將政策儲存到名為 *trust-policy.json* 的檔案。

------
#### [ JSON ]

****  

   ```
   {
       "Version":"2012-10-17",		 	 	 
       "Statement": {
           "Effect": "Allow",
           "Principal": { "AWS": "arn:aws:iam::123456789012:root" },
           "Action": "sts:AssumeRole"
       }
   }
   ```

------

1. 建立 IAM 政策文件，如下所示。將政策儲存到名為 *policy.json* 的檔案。

------
#### [ 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"
         ]
       }
     ]
   }
   ```

------

1. 建立 IAM 角色。

   ```
   aws iam create-role \
   --role-name "elasticache-iam-auth-app" \
   --assume-role-policy-document file://trust-policy.json
   ```

1. 建立 IAM 政策。

   ```
   aws iam create-policy \
     --policy-name "elasticache-allow-all" \
     --policy-document file://policy.json
   ```

1. 將 IAM 政策連接至角色。

   ```
   aws iam attach-role-policy \
    --role-name "elasticache-iam-auth-app" \
    --policy-arn "arn:aws:iam::123456789012:policy/elasticache-allow-all"
   ```

1. 建立已啟用 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"
   ```

1. 建立使用者群組並連接使用者。

   ```
   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
   ```

## 連接
<a name="auth-iam-Connecting"></a>

**以字符做為密碼進行連線**

首先，您需要使用 [AWS SigV4 預先簽章的請求](https://docs.aws.amazon.com//general/latest/gr/sigv4-signed-request-examples.html)，產生短期 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 with 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());
    }
}
```

**使用憑證提供者進行連線**

以下程式碼說明如何使用 IAM 身分驗證憑證提供者向 ElastiCache 進行身分驗證。

```
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 with 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());
    }
}
```