Authenticating with IAM - Amazon MemoryDB for Redis

Authenticating with IAM

Overview

With IAM Authentication you can authenticate a connection to MemoryDB using AWS IAM identities, when your cluster is configured to use Redis version 7 or above. This allows you to strengthen your security model and simplify many administrative security tasks. With IAM Authentication you can configure fine-grained access control for each individual MemoryDB cluster and MemoryDB user and follow least-privilege permissions principles. IAM Authentication for MemoryDB Redis works by providing a short-lived IAM authentication token instead of a long-lived MemoryDB user password in the Redis AUTH or HELLO command. For more information about the IAM authentication token, refer to the Signature Version 4 signing process in the the AWS General Reference Guide and the code example below.

You can use IAM identities and their associated policies to further restrict Redis access. You can also grant access to users from their federated Identity providers directly to MemoryDB clusters.

To use AWS IAM with MemoryDB, you first need to create a MemoryDB user with authentication mode set to IAM, then you can create or reuse an IAM identity. The IAM identity needs an associated policy to grant the memorydb:Connect action to the MemoryDB cluster and MemoryDB user. Once configured, you can create an IAM authentication token using the AWS credentials of the IAM user or role. Finally you need to provide the short-lived IAM authentication token as a password in your Redis client when connecting to your MemoryDB cluster node. A Redis client with support for credentials provider can auto-generate the temporary credentials automatically for each new connection. MemoryDB will perform IAM authentication for connection requests of IAM-enabled MemoryDB users and will validate the connection requests with IAM.

Limitations

When using IAM authentication, the following limitations apply:

  • IAM authentication is available when using Redis engine version 7.0 or above.

  • The IAM authentication token is valid for 15 minutes. For long-lived connections, we recommend using a Redis client that supports a credentials provider interface.

  • An IAM authenticated connection to MemoryDB will automatically be disconnected after 12 hours. The connection can be prolonged for 12 hours by sending an AUTH or HELLO command with a new IAM authentication token.

  • IAM authentication is not supported in MULTI EXEC commands.

  • Currently, IAM authentication doesn't support all global condition context keys. For more information about global condition context keys, see AWS global condition context keys in the IAM User Guide.

Setup

To setup IAM authentication:

  1. Create a cluster

    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. Create an IAM trust policy document, as shown below, for your role that allows your account to assume the new role. Save the policy to a file named trust-policy.json.

    { "Version": "2012-10-17", "Statement": { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::123456789012:root" }, "Action": "sts:AssumeRole" } }
  3. Create an IAM policy document, as shown below. Save the policy to a file named 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. Create an IAM role.

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

    aws iam create-policy \ --policy-name "memorydb-allow-all" \ --policy-document file://policy.json
  6. Attach the IAM policy to the role.

    aws iam attach-role-policy \ --role-name "memorydb-iam-auth-app" \ --policy-arn "arn:aws:iam::123456789012:policy/memorydb-allow-all"
  7. Create a new IAM-enabled user.

    aws memorydb create-user \ --user-name iam-user-01 \ --authentication-mode Type=iam \ --access-string "on ~* +@all"
  8. Create an ACL and attach the user.

    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

Connecting

Connect with token as password

You first need to generate the short-lived IAM authentication token using an AWS SigV4 pre-signed request. After that you provide the IAM authentication token as a password when connecting to a MemoryDB cluster, as shown in the example below.

String userName = "insert user name" String clusterName = "insert cluster name" 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 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();

Below is the definition for 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()); } }

Connect with credentials provider

The code below shows how to authenticate with MemoryDB using the IAM authentication credentials provider.

String userName = "insert user name" String clusterName = "insert cluster name" 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 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();

Below is an example of a Lettuce Redis cluster client that wraps the IAMAuthTokenRequest in a credentials provider to auto-generate temporary credentials when needed.

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