tl;dr Working in Go, and had to write my own token signing method connect to Valkey Instance in Elasticache. Keep getting error: "WRONGPASS invalid username-password pair or user is disabled."
import (
...
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/elasticache"
)
func (r *RedisHealthController) setupCustomIamAuthTokenAltAlt(ctx context.Context, username, replicationGroupID string) string {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {...}
const (
requestMethod = "GET"
paramAction = "Action"
paramUser = "User"
actionName = "connect"
serviceName = "elasticache"
tokenExpirySeconds = 900
)
region := cfg.Region
ecClient := elasticache.NewFromConfig(cfg)
describeOutput, err := ecClient.DescribeReplicationGroups(ctx, &elasticache.DescribeReplicationGroupsInput{
ReplicationGroupId: &replicationGroupID, // Use the correct ID here
})
if err != nil {...}
if len(describeOutput.ReplicationGroups) == 0 || len(describeOutput.ReplicationGroups[0].NodeGroups) == 0 {...}
endpointAddress := *describeOutput.ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint.Address
endpointPort := *describeOutput.ReplicationGroups[0].NodeGroups[0].PrimaryEndpoint.Port
authURL := url.URL{
Scheme: "https",
Host: fmt.Sprintf("%s:%d", endpointAddress, endpointPort),
Path: "/",
}
queryParams := url.Values{}
queryParams.Set(paramAction, actionName)
queryParams.Set(paramUser, username)
authURL.RawQuery = queryParams.Encode()
req, err := http.NewRequestWithContext(ctx, requestMethod, authURL.String(), nil)
if err != nil {...}
credentials, err := cfg.Credentials.Retrieve(ctx)
if err != nil {...}
presigner := v4.NewSigner()
signedURL, _, err := presigner.PresignHTTP(
ctx,
credentials,
req,
emptyPayloadHash, // Hash for an empty string, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
serviceName, // "elasticache"
region,
time.Now().UTC().Add(time.Duration(tokenExpirySeconds)*time.Second),
)
if err != nil {...}
return signedURL
}
I then establish a client using:
endpoint := fmt.Sprintf("%s:%s", redisURL, redisPort)
token := r.setupCustomIamAuthToken(ctx, details, username)
if token == "" {...}
rdb = redis.NewClient(&redis.Options{
Addr: endpoint,
Username: username,
Password: token,
DB: 0,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
},
})
I then do a simple ping and get back the WRONGPASS error. As an aside, I have also used cloud shell to try connecting to the cluster and I get the same error. So, this should not be a vpc/firewall/SSL issue.
The issue is that there is a lacking of documentation on all of this. The method above was derived from Java code found in AWS documentation on using IAM short lived token generation for elasticache, combined with rewriting some of the code in the rds auth method in the aws sdk.
Unfortunately, I have no way of knowing if the token is generated in a correct format as there are no example that I can use. The token looks like this:
"https://master.<elasticache-instance-name>.<connection-string>.usw2.cache.amazonaws.com:6379/?Action=connect&User=<elasticache-user-id>&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=<...>&X-Amz-Date=20250701T021930Z&X-Amz-Security-Token=<...>&X-Amz-SignedHeaders=host&X-Amz-Signature=<...>"
Any ideas of where I can find resources on how to do establish this connection? Let me know if you need info on terraform scripts or iam roles (I suspect the roles are correct as I would be blocked earlier if the roles/policies were bad).
Some quick things I noticed:
You should drop "https://" part. This is definitely an issue. The token should start with the cluster name, not "http"
You are using the endpoint url/port as the cluster name. It should not look like a url. It's the actual cache name that is alphanumeric with hyphens.
Not sure if this is a contributing factor, but your querystring is missing signv4Expires which should be set with the value "900". This is not documented on their website but it's documented in the v4 signer library.