AWS RDS - 使用 Rails 的 IAM 数据库身份验证

AWS RDS - IAM Database Authentication with Rails

我希望在 Rails 上使用 AWS RDS IAM database authentication 和 Ruby,因为它为 AWS 用户提供了一种方便的方式来管理数据库权限并避免将数据库密码存储在他们的代码库。

在高层次上,它的工作原理是根据您的 AWS 凭据生成密码以连接到仅在 15 分钟内有效的数据库。如果您想在 15 分钟后再次连接,则需要生成一个新密码。

此密码可以使用 AWS Ruby SDK 轻松生成,因此理论上可以像这样嵌入 config/database.yml

production:
  adapter: mysql2
  host: db_host
  database: db_name
  username: db_user
  password: <%=
              Aws::RDS::AuthTokenGenerator
                .new(credentials: Aws::InstanceProfileCredentials.new)
                .auth_token(
                  region: 'us-east-1',
                  endpoint: 'db_host:3306',
                  user_name: 'db_user'
                )
            %>

然而,据我所知,config/database.yml 仅在启动时计算一次,并在 Rails 的生命周期内保持缓存状态。

因此,通过使用这种方法,Rails 服务器最初会成功连接到数据库,但是如果在第一个 15 分钟后的任何时候 window Rails 试图打开新的数据库连接或重新连接断开的连接,现在过期的凭据将被拒绝。

使用 Rails 进行 IAM 数据库身份验证的最佳方法是什么?我是否需要以某种方式使用在每次建立连接时重新评估密码的数据库配置?

我想了想这个问题的解决方案,我想出的最好方法是 monkeypatching Mysql2::Client#initialize 这样你就可以启用 IAM 数据库身份验证,它会透明地将密码属性更改为 RDS密码。这似乎适用于 Rails 5.2.2.1 和 mysql 0.5.2.

一个重要的警告是您不能启用客户端的重新连接功能,因为我们需要确保 Rails 在发生连接错误时回收客户端(这似乎在上面的默认情况下发生 Rails版本).

# config/database.rb
require 'aws-sdk-rds'
require 'mysql2'

Aws.config.update(
  region: 'your_region',
)

class RdsIamPasswordGenerator
  def self.generate(region, host, user, port)
    Aws::RDS::AuthTokenGenerator
      .new(credentials: Aws::InstanceProfileCredentials.new)
      .auth_token(
        region: region,
        endpoint: host.to_s + ':' + port.to_s,
        user_name: user
      )
  end
end

module MysqlClientIamMonkeyPatch
  def initialize(opts = {})
    opts         = opts.dup
    aws_iam_auth = opts.delete(:aws_iam_auth)

    if aws_iam_auth
      raise ArgumentError, 'reconnect must be false if aws_iam_auth is true' if opts[:reconnect]

      user = opts[:username] || opts[:user]
      host = opts[:host] || opts[:hostname]
      port = opts[:port] || 3306

      raise ArgumentError, 'username/user and host/hostname must be present' if user.nil? || host.nil?

      opts.delete(:pass)
      opts.delete(:password)

      opts[:password] = RdsIamPasswordGenerator.generate(Aws.config[:region], host, user, port)
      opts[:enable_cleartext_plugin] = true # Necessary for IAM auth
    end

    super(opts)
  end
end

Mysql2::Client.prepend(MysqlClientIamMonkeyPatch)
# config/boot.rb
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)

require 'bundler/setup' # Set up gems listed in the Gemfile.
require 'bootsnap/setup' # Speed up boot time by caching expensive operations.

require_relative './database' # Handles patching in IAM auth
# config/database.yml
production:
  adapter: mysql2
  database: production
  ssl_mode: verify_identity
  sslverify: true
  sslca: /opt/aws/rds-combined-ca-bundle.pem
  aws_iam_auth: true
  host: db_host
  username: db_user
  password: null