Skip to content

Commit

Permalink
Initial pass adding AWS IAM Authentication brianmario#1263
Browse files Browse the repository at this point in the history
This adds AWS IAM authentication as a replacement for defining a
password in the configuration.

When the configuration option :use_iam_credentials = 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_credentials = 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.
  • Loading branch information
matt-domsch-sp committed Nov 10, 2024
1 parent f6a9b68 commit 229d236
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 0 deletions.
61 changes: 61 additions & 0 deletions lib/mysql2/aws_iam_auth.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
require 'singleton'
require 'aws-sdk-rds'

module Mysql2
class AwsTokenAuth
include Singleton

# Tokens are valid for up to 15 minutes.
# We will assume ours expire in 14 minutes to be safe.
TOKEN_EXPIRES_IN = (60*14) # 14 minutes

def initialize
@mutex = Mutex.new
# Key identifies a unique set of authentication parameters
# Value is a Hash
# :password is the token value
# :expires_at is (just before) the token was generated plus 14 minutes
@passwords = {}
@generator = Aws::RDS::AuthTokenGenerator.new
end

def password(user, host port, opts)
params = to_params(user, host, port, opts)
key = key_from_params(params)
passwd = nil
AwsTokenAuth.instance.mutex.synchronize do
begin
if @passwords[key][:password] && Time.now.utc < @passwords[key][:expires_at]
passwd = @passwords[key][:password]
end
rescue KeyError
end
end
if passwd return passwd

AwsTokenAuth.instance.mutex.synchronize do
@passwords[key] = {}
@passwords[key][:expires_at] = Time.now.utc + TOKEN_EXPIRES_IN
@passwords[key][:password] = password_from_iam(params)
end
end

def password_from_iam(params)
@generator.auth_token(params)
end

def to_params(user, host, port, opts)
params = {}
params[:region] = opts[:host_region] || ENV['AWS_REGION']
params[:endpoint] = "#{host}:#{port}"
params[:user_name] = user
params
end

def key_from_params(params)
return "#{params[:user_name]}/#{params[:endpoint]}/#{params[:port]}/#{params[:region]}"
end


end
end
5 changes: 5 additions & 0 deletions lib/mysql2/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ def initialize(opts = {})
socket = socket.to_s unless socket.nil?
conn_attrs = parse_connect_attrs(opts[:connect_attrs])

if opts[:use_iam_credentials]
aws = Mysql2::AwsTokenAuth.new
pass = aws.password(user, host, port, opts)
end

connect user, pass, host, port, database, socket, flags, conn_attrs
end

Expand Down
1 change: 1 addition & 0 deletions mysql2.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s|
s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient'

s.add_runtime_dependency 'bigdecimal'
s.add_runtime_dependency 'aws-sdk-rds'
end

0 comments on commit 229d236

Please sign in to comment.