mysql2 icon indicating copy to clipboard operation
mysql2 copied to clipboard

Initial pass adding AWS IAM Authentication #1263

Open matt-domsch-sp opened this issue 1 year ago • 4 comments

This adds AWS IAM authentication as a replacement for defining a password in the configuration.

When the configuration option :use_iam_authentication = true, an authentication token (password) will be fetched from IAM and cached for the next 14 minutes (tokens expire in 15 minutes). These can then be reused by all new connections until it expires, at which point a new token will be fetched when next needed.

To allow for multiple Mysql2::Client configurations to multiple servers, the cache is keyed by database username, host name, port, and region.

Two new configuration options are necessary:

  • :use_iam_authentication = true
  • :host_region is a string region name, e.g. 'us-east-1'. If not set, ENV['AWS_REGION'] will be used. If this is not present, authenticaiton will fail.

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/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.html for details on these steps.

matt-domsch-sp avatar Nov 10 '24 23:11 matt-domsch-sp

This behaves as expected with a simple test program below, and lints without any additional messages. Failure to have the aws-sdk-rds gem already present results in a LoadError only when option :use_iam_authentication = true. No change in behavior when :use_iam_authentication is not provided or is false.

#!ruby

require 'mysql2'

opts={}
opts[:use_iam_authentication] = true
opts[:host] = 'dbserver'
opts[:port] = '3306'
opts[:host_region] = 'us-east-1'
opts[:username] = 'dbuser'
opts[:ssl_mode] = :verify_identity

aws = Mysql2::AwsIamAuth.instance
puts "passwords = #{aws.passwords.count}" # => 0 as expected

clients = []
(1..10).to_a.each do

 client = Mysql2::Client.new(opts)
 clients.append(client)
 puts "passwords = #{aws.passwords.count}" # => yields 1 as expected because it's reused for each connection
 results = client.query("SHOW DATABASES")
 results.each do |row|
   puts row # => yields the databases visible to the user as expected
 end
end

matt-domsch-sp avatar Nov 12 '24 04:11 matt-domsch-sp

https://github.com/brianmario/mysql2/pull/1370 suggests allowing use of an external password provider. That would make it easier for solutions such as https://github.com/floor114/mysql2-aws_rds_iam to drop in.

matt-domsch-sp avatar Dec 09 '24 14:12 matt-domsch-sp

Would love to see this pushed throught. Thankyou for working on this

patelvp avatar Jan 21 '25 17:01 patelvp

I have concerns that by doing the token fetch and refresh in a higher-level add-on gem such as https://github.com/floor114/mysql2-aws_rds_iam, which specifically requires that the driver-level reconnect mechanism be disabled (because it can't reconnect with an expired token, and it can't fetch a new token itself), that "ordinary" occasional activities such as database failovers won't get properly handled without driver-level reconnect available.

Has anyone using the floor114/mysql2-aws_rds_iam gem had problems with database failover?

matt-domsch-sp avatar Mar 28 '25 15:03 matt-domsch-sp