Skip to content

Commit 6e09c67

Browse files
Initial pass adding AWS IAM Authentication #1263
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.
1 parent f6a9b68 commit 6e09c67

File tree

4 files changed

+94
-2
lines changed

4 files changed

+94
-2
lines changed

README.md

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,8 +284,10 @@ Mysql2::Client.new(
284284
:get_server_public_key = true/false,
285285
:default_file = '/path/to/my.cfg',
286286
:default_group = 'my.cfg section',
287-
:default_auth = 'authentication_windows_client'
288-
:init_command => sql
287+
:default_auth = 'authentication_windows_client',
288+
:init_command => sql,
289+
:use_iam_authentication => true/false,
290+
:host_region,
289291
)
290292
```
291293

@@ -348,6 +350,30 @@ When secure_auth is enabled, the server will refuse a connection if the account
348350
The MySQL 5.6.5 client library may also refuse to attempt a connection if provided an older format password.
349351
To bypass this restriction in the client, pass the option `:secure_auth => false` to Mysql2::Client.new().
350352

353+
### AWS IAM Authentication
354+
355+
You may use AWS IAM Authentication instead of setting a password in
356+
the configuration. A temporary token used in place of the password
357+
will be fetched as necessary and used for connections until it
358+
expires. The value for :host_region will either use the one provided,
359+
or if not provided, the environment variable AWS_REGION.
360+
361+
| `:use_iam_authentication` | true |
362+
| --- | --- |
363+
| `:username` | The database username configured to use IAM Authentication |
364+
| `:host` | The database host |
365+
| `:port` | The database port |
366+
| `:host_region` | An AWS region name, e.g. `us-east-1` |
367+
368+
As prerequisites, you must enable IAM authentication on the RDS
369+
instance, create an IAM policy, attach the policy to the target IAM
370+
user or role, create the database user set to use the AWS
371+
Authentication Plugin, and then run your ruby code using that IAM user or
372+
role. See
373+
[AWS documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.Connecting.html)
374+
for details on these steps.
375+
376+
351377
### Flags option parsing
352378

353379
The `:flags` parameter accepts an integer, a string, or an array. The integer

lib/mysql2/aws_iam_auth.rb

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
require 'singleton'
2+
require 'aws-sdk-rds'
3+
4+
module Mysql2
5+
class AwsTokenAuth
6+
include Singleton
7+
8+
# Tokens are valid for up to 15 minutes.
9+
# We will assume ours expire in 14 minutes to be safe.
10+
TOKEN_EXPIRES_IN = (60*14) # 14 minutes
11+
12+
def initialize
13+
@mutex = Mutex.new
14+
# Key identifies a unique set of authentication parameters
15+
# Value is a Hash
16+
# :password is the token value
17+
# :expires_at is (just before) the token was generated plus 14 minutes
18+
@passwords = {}
19+
@generator = Aws::RDS::AuthTokenGenerator.new
20+
end
21+
22+
def password(user, host port, opts)
23+
params = to_params(user, host, port, opts)
24+
key = key_from_params(params)
25+
passwd = nil
26+
AwsTokenAuth.instance.mutex.synchronize do
27+
begin
28+
if @passwords[key][:password] && Time.now.utc < @passwords[key][:expires_at]
29+
passwd = @passwords[key][:password]
30+
end
31+
rescue KeyError
32+
end
33+
end
34+
if passwd return passwd
35+
36+
AwsTokenAuth.instance.mutex.synchronize do
37+
@passwords[key] = {}
38+
@passwords[key][:expires_at] = Time.now.utc + TOKEN_EXPIRES_IN
39+
@passwords[key][:password] = password_from_iam(params)
40+
end
41+
end
42+
43+
def password_from_iam(params)
44+
@generator.auth_token(params)
45+
end
46+
47+
def to_params(user, host, port, opts)
48+
params = {}
49+
params[:region] = opts[:host_region] || ENV['AWS_REGION']
50+
params[:endpoint] = "#{host}:#{port}"
51+
params[:user_name] = user
52+
params
53+
end
54+
55+
def key_from_params(params)
56+
return "#{params[:user_name]}/#{params[:endpoint]}/#{params[:region]}"
57+
end
58+
59+
end
60+
end

lib/mysql2/client.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ def initialize(opts = {})
9494
socket = socket.to_s unless socket.nil?
9595
conn_attrs = parse_connect_attrs(opts[:connect_attrs])
9696

97+
if opts[:use_iam_authentication]
98+
aws = Mysql2::AwsTokenAuth.new
99+
pass = aws.password(user, host, port, opts)
100+
end
101+
97102
connect user, pass, host, port, database, socket, flags, conn_attrs
98103
end
99104

mysql2.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ Mysql2::GEMSPEC = Gem::Specification.new do |s|
2424
s.metadata['msys2_mingw_dependencies'] = 'libmariadbclient'
2525

2626
s.add_runtime_dependency 'bigdecimal'
27+
s.add_runtime_dependency 'aws-sdk-rds'
2728
end

0 commit comments

Comments
 (0)