Using IAM authentication for Amazon MemoryDB and ElastiCache
How to securely connect your Go applications using IAM authentication
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.
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"
]
}
}
]
}
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>"
]
}
]
}
- 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.
1
2
git clone https://github.com/build-on-aws/aws-redis-iam-auth-golang
cd aws-redis-iam-auth-golang
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
1
./run.sh
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
You can refer to the authentication code in the GitHub repository
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)
//...
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(),
)
//...
}
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},
})
},
})
//...
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>"
]
}
]
}
SERVICE_NAME
environment variable to elasticache
as well as the endpoint URL, cluster name and IAM username for the ElastiCache instance.redis.NewClient
(instead of redis.NewClusterClient
). The CredentialsProvider
option will be available nonetheless.MULTI EXEC
commands. For a complete list, refer to the documentation.Any opinions in this post are those of the individual author and may not reflect the opinions of AWS.