goamazon-iamamazon-elasticachevalkey

Connecting to Elasticache Valkey Using IAM Role


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).


Solution

  • Some quick things I noticed: