logo
Using IAM authentication for Redis on AWS

Using IAM authentication for Redis on AWS

How to securely connect your Go applications to Amazon MemoryDB (or ElastiCache) for Redis using IAM

AG
Abhishek Gupta
Amazon Employee
Published Dec 7, 2023
Amazon MemoryDB for Redis has supported username/password based authentication using Access Control Lists since the very beginning. But using IAM based authentication allows you to associate IAM users and roles with MemoryDB users so that applications can use IAM credentials to authenticate to the MemoryDB cluster. With this authentication mode, you don't need to use a (long-lived) user password. Instead, you use an authentication token generated using AWS Signature Version 4.
There are many benefits to this approach. Instead of managing username and password based credentials, you can use IAM to centrally manage access to MemoryDB clusters. For client applications running on Amazon EC2, Amazon EKS, AWS Lambda, AWS App Runner etc., you can inject these credentials (depending on the platform e.g. profile credentials in EC2 and instance role in App Runner) - this provides greater security.
MemoryDB documentation has an example for a Java application with the Lettuce client. The process is similar for other languages, but you still need to implement it. So, let's learn how to do it for a Go application with the widely used go-redis client.
As a bonus, this is also applicable to ElastiCache for Redis, which also supports IAM authentication. There are minor differences which I will list at the end of the blog.
Let's get started....

Start by creating a MemoryDB user, Access Control list (ACL) and add the user to it. Make sure to use IAM as the authentication type.
create user
create user
Now, create the MemoryDB cluster. Make sure to choose version 7.0 or above and choose the ACL you created before.
choose version
choose version
While the cluster is being provisioned, you can create an IAM role. Use the following Trust Relationship to allow connectivity from an EC2 instance:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole"
],
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
}
}
]
}
Use the following permissions - replace the MemoryDB cluster, IAM username, AWS account ID and region:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
memorydb:Connect"
],
"
Resource": [
"
arn:aws:memorydb:<enter aws region>:<enter aws account ID>:cluster/<enter memorydb cluster name>",
"
arn:aws:memorydb:<enter aws region>:<enter aws account ID>:user/<enter memorydb iam user name>"
]
}
]
}

Create an AWS Cloud9 environment to execute the client application that will connect to MemoryDB.
  • Make sure to use same VPC and subnet as the MemoryDB cluster
  • Update the MemoryDB subnet group to add inbound rule for the client application connectivity.
Refer to the Getting started with Redis on AWS - the easy way! blog post that already has this covered.

Once the MemoryDB cluster and Cloud9 instance are ready, go ahead and execute the client application.
Open the Cloud9 instance terminal, clone the following GitHub repository:
1
2
git clone https://github.com/build-on-aws/aws-redis-iam-auth-golang
cd aws-redis-iam-auth-golang
Update the run.sh file to enter the MemoryDB cluster endpoint, and username. For example:
1
2
3
4
export SERVICE_NAME=memorydb
export CLUSTER_NAME=demo-iam-cluster
export CLUSTER_ENDPOINT=clustercfg.demo-iam-cluster.xyzxy4.memorydb.us-west-1.amazonaws.com:6379
export USERNAME=demo-iam-user
To run the application:
1
./run.sh
Once the app is up and running, simply invoke it's HTTP endpoints to verify that it works with IAM authentication.
1
2
3
4
5
6
7
8
9
# expect 200 OK - this will create two keys
curl -i -X POST -d '{"key":"foo1", "value":"bar2"}' http://localhost:8080
curl -i -X POST -d '{"key":"foo2", "value":"bar2"}' http://localhost:8080

#
expect a JSON response
curl -i http://localhost:8080/foo1

#
expect a HTTP 404 since the key does not exist
curl -i http://localhost:8080/not_there
The application super simple. That's on purpose. Let's move on to the important bits, which is about the IAM authentication part.

You can refer to the authentication code in the GitHub repository
1) We first create a HTTP GET request that contains the cluster name as part of the URL along with username, the action (connect) and token expiry (900 seconds) as query parameters:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//...
queryParams := url.Values{
"Action": {connectAction},
"User": {userName},
"X-Amz-Expires": {strconv.FormatInt(int64(tokenValiditySeconds), 10)},
}

authURL := url.URL{
Host: clusterName,
Scheme: "http",
Path: "/",
RawQuery: queryParams.Encode(),
}

req, err := http.NewRequest(http.MethodGet, authURL.String(), nil)
//...
2) The request is then signed using PresignHTTP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func (atg AuthTokenGenerator) Generate() (string, error) {

signedURL, _, err := atg.signer.PresignHTTP(
context.Background(),
atg.credentials,
atg.req,
hexEncodedSHA256EmptyString,
atg.serviceName,
atg.region,
time.Now().UTC(),
)

//...
}
3) The token generator is invoked in a CredentialsProvider during client creation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//...
generator, err := auth.New(serviceName, clusterName, username, region)

client = redis.NewClusterClient(
&redis.ClusterOptions{
Username: username,
Addrs: []string{clusterEndpoint},
NewClient: func(opt *redis.Options) *redis.Client {

return redis.NewClient(&redis.Options{
Addr: opt.Addr,

CredentialsProvider: func() (username string, password string) {
token, err := generator.Generate()
return opt.Username, token
},
TLSConfig: &tls.Config{InsecureSkipVerify: true},
})
},
})
//...
That's it in terms of a quick overview. But wait, there is more...

The same approach applies - with a few changes. First, the IAM policy has to be updated to reflect ElastiCache resources (obviously!)
For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"Version": "2012-10-17",
"Statement": [
{
"Effect" : "Allow",
"Action" : [
"elasticache:connect"
],
"Resource" : [
"arn:aws:elasticache:<enter aws region>:<enter aws account id>:<enter type - replicationgroup or serverlesscache>:<enter replicationgroup or serverlesscache name>",
"arn:aws:elasticache:<enter aws region>:<enter aws account id>:user:<enter username>"
]
}
]
}
Before you run the application, update the SERVICE_NAME environment variable to elasticache as well as the endpoint URL, cluster name and IAM username for the ElastiCache instance.
This example assumes you are using a Redis Cluster connection mode (which is the only option in case of MemoryDB). But in case of ElastiCache be mindful of whether you are using cluster-mode enabled configuration. If not, you will have to tweak the code to use redis.NewClient (instead of redis.NewClusterClient). The CredentialsProvider option will be available nonetheless.

Using IAM authentication has its benefits. Instead of managing username and passwords in multiple locations/applications, delegate the heavy lifting to IAM. All you need is to provide configure appropriate permissions (following the principle of "least privilege").
But you also need to be aware of the limitations. For e.g. IAM authentication is not supported in MULTI EXEC commands. For a complete list, refer to the documentation.
Have you tried using IAM authentication with MemoryDB or ElastiCache for Redis in other programming languages? Let me know.
Happy building!

Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.