Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: IAM Authentication in place of password #1299

Closed
matt-domsch-sp opened this issue Dec 8, 2024 · 10 comments · Fixed by redis-rb/redis-client#222
Closed

Feature request: IAM Authentication in place of password #1299

matt-domsch-sp opened this issue Dec 8, 2024 · 10 comments · Fixed by redis-rb/redis-client#222

Comments

@matt-domsch-sp
Copy link

AWS supports the use of IAM Authentication for Elasticache Redis and Valkey. This allows code to retrieve a short-lived token to use in place of the password when creating a connection. The tokens can be reused for up to 15 minutes before needing to be regenerated. Doing so allows applications to eliminate the need to create, store, secure, and rotate passwords used with Redis.

As prerequisites, you must enable IAM authentication on the RDS instance, create an IAM policy, attach the policy to the target IAM user or role, create the database user set to use the AWS Authentication Plugin, and then run your ruby code using that user or role. See https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/auth-iam.html for details on these steps.

This is to request adding IAM Authentication into the ruby redis driver. Equivalent changes are being requested or already made in other Ruby drivers for postgres, mysql, and mongodb.

@byroot
Copy link
Collaborator

byroot commented Dec 9, 2024

Supporting this natively would require depending on an HTTP client, and probably a bunch of aws gems, which isn't acceptable.

However, I think if we could allow the password: config to take a Proc, so you could plug all that yourself:

r = Redis.new(password: -> (user) { AWS.generate_password } )

@matt-domsch-sp
Copy link
Author

That's a good approach. The postgres driver team didn't want code directly in the driver either, so that's being patched in by another gem. Having a method to explicitly have a hook would be cleaner, and could then let that code live in an external gem that requires the AWS SDK gems necessary to authenticate.

@mbyio
Copy link

mbyio commented Dec 10, 2024

However, I think if we could allow the password: config to take a Proc, so you could plug all that yourself

Yeah this is the approach used by a bunch of other drivers that do IAM authentication, like for kafka. Let the user deal with generating and/or caching the token if needed. In addition to AWS IAM token generation, it can also be used for password rotation, like if you fetch the password from a secrets store.

byroot added a commit to redis-rb/redis-client that referenced this issue Dec 10, 2024
Fix: redis/redis-rb#1299

Makes it easy to implement short lived password authentication strategies.
@mappei
Copy link

mappei commented Jan 20, 2025

Hi @matt-domsch-sp, @byroot , sorry to bother you. I'm a beginner in Ruby, and we're currently facing an issue: how to allow IAM-authenticated users to access ElastiCache for Redis.
Based on previous comments, I tried to use this feature but encountered some difficulties.

I use the following code to generate a token:

def generate_token(elasticache_name, connect_user, credentials)
  signer = Aws::Sigv4::Signer.new(
    service: 'elasticache',
    region: "#{$aws_region}",
    credentials_provider: credentials
  )
  url = "#{elasticache_name}/?Action=connect&User=#{connect_user}" 
  signer.presign_url(
    http_method: 'GET',
    url: url,
    expires_in: 3600
  ).to_s
end

And I pass this token to Redis for authentication and authorized access.

redis = Redis.new(
        host: redis_host,
        port: redis_port,
        ssl: true,
        password: generate_token(elasticache_replication_group, elasticache_user, Aws.config[:credentials]),
        username: elasticache_user
      )

However, after running the code, I encountered an error with the following message:

WRONGPASS invalid username-password pair or user is disabled.

I printed out the generated token, and the content is as follows:
replication_group_name/?Action=connect&User=elastcache_user&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=ASIA3......&X-Amz-Date=20250120T033239Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEKT%2.....&X-Amz-SignedHeaders=host&X-Amz-Signature=b34575843caf15f8b9.....

There might be something wrong with how I'm using it, but I'm not sure how to fix it. Could you please review my approach and point out any issues?

Best wishes~~

@matt-domsch-sp
Copy link
Author

My team and I have not yet gotten to the point where we have made use of IAM authN here - we have quite a few other updates that have to occur before we can upgrade to this version of the driver and make use of this feature. This looks basically right to me. I trust you've created the user and enabled IAM authN on the redis cluster itself: https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/auth-iam.html

The 12-hour connection limit without a new AUTH or HELLO command caught me off guard too. I'll have to look into how we're handling connections. It'd be nice if this were handled automatically rather than connections just getting dropped and the client needing to recognize and reconnect, but as connection drops can happen for failover, maybe the same reconnect mechanism will just work.

@emmanuel-chime
Copy link

Hey @mappei, your code is very close to working.
If you update your generate_token method to this it should work:

def generate_token(elasticache_name, connect_user, credentials, protocol: 'https://')
  signer = Aws::Sigv4::Signer.new(
    service: 'elasticache',
    region: "#{$aws_region}",
    credentials_provider: credentials
  )
  url = "#{protocol}#{elasticache_name}/?Action=connect&User=#{connect_user}" 
  signed_url = signer.presign_url(
    http_method: 'GET',
    url: url
  )

  signed_url.to_s.gsub(protocol, '')
end

@mappei
Copy link

mappei commented Feb 6, 2025

@emmanuel-chime Very thanks for your feedback. Later, I modified the implementation to match the approach you provided, but I still encountered the same error. This issue has been troubling me for a long time, and I'm not sure how to debug it.

def generate_token(elasticache_name, connect_user, credentials, region)
  signer = Aws::Sigv4::Signer.new(
    service: 'elasticache',
    region: "#{region}",
    credentials_provider: credentials
  )
  query_params = {
    "Action" => "connect",
    "User" => connect_user
  }
  uri = URI("http://#{elasticache_name}/")
  uri.query = URI.encode_www_form(query_params)

  signer.presign_url(
    http_method: 'GET',
    url: uri.to_s,
    expires_in: 3600,
    headers: {
      "host" => elasticache_name,
    }
  ).to_s.gsub('http://’, '')
end

Since my credentials are obtained through Assume Role, I also compared the pre-signed result X-Amz-Security-Token parameter with the session token obtained from Assume Role, and they have the same value.

Do you have similar experience with this? How did you handle it? Could you provide some references or suggestions?

Best wishes~~

@mappei
Copy link

mappei commented Feb 7, 2025

This issue has been resolved. After changing the expiration time from 3600 seconds to 900 seconds, I was able to successfully connect to ElastiCache.

@mperham
Copy link
Contributor

mperham commented Feb 28, 2025

@byroot
Copy link
Collaborator

byroot commented Feb 28, 2025

Yes, it should receive the same patch.

byroot added a commit to redis-rb/redis-client that referenced this issue Mar 3, 2025
byroot added a commit to redis-rb/redis-client that referenced this issue Mar 3, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants