我们使用 AWS SecretsManager (SM) 来存储 Rails 应用程序的轮换数据库密码。但是,当 SM 轮换密码时,我们收到“FATAL:用户密码验证失败”。
密码在database.yml中读取
password: '<%= AwsSecretService.new.get_db_pwd(ENV['DATABASE_USERNAME']) if Rails.env.production? %>'
问题似乎是 ActiveRecord 在初始化时缓存 database.yml 中的数据库凭据,并且不重新解析 database.yml。
我的想法是以某种方式捕获身份验证失败错误并重新初始化 ActiveRecord。使用以下命令:
ActiveRecord::Base.establish_connection(::Rails.application.config.database_configuration[::Rails.env])
我不知道如何捕获错误。我是否需要在 application_record.rb 中放置一个错误处理程序,因为所有 activerecord 模型都继承自它?
或者使用某种观察者会更好吗?
想法?
我也在研究这个问题,也遇到了同样的问题。 Rails 似乎只在应用程序启动时读取一次密码。如果随后轮换密码,某些连接将成功(如果它们已经建立),但池中的新连接将失败,因为它们将尝试使用现已过时的密码。
如果 Rails 可以“强制”在启动时创建池中的所有连接,那么这将起作用。或者,如果 Rails 可以在每次建立新连接时动态提取密码,那也可以。 但是,我还没有能力做这两件事。
lib/aws_secret_manager.rb:
class AwsSecretsManager
class << self
def database_config
secret_json = fetch_secret_json
return {} unless secret_json
fetch_database_config(secret_json)
end
private
def fetch_secret_json
client = Aws::SecretsManager::Client.new(region: ENV.fetch('AWS_REGION', nil))
secret_value_response = client.get_secret_value(secret_id: ENV.fetch('AWS_SECRET_ID', nil))
secret_value_response.secret_string
end
def fetch_database_config(secret_json)
secret_hash = JSON.parse(secret_json, symbolize_names: true)
secret_hash.slice(:host, :port, :username, :password)
end
end
end
Rails 使用
ActiveRecord::DatabaseConfigurations::*
类来存储数据库配置,并调用
ActiveRecord::DatabaseConfigurations#resolve(config_or_env)
方法来获取新连接的配置。要为新连接应用新密码,您可以覆盖此方法并将更改添加到 ActiveRecord::DatabaseConfigurations
类。lib/active_record/aws_database_configurations.rb:
module ActiveRecord
module AwsDatabaseConfigurations
def resolve(config)
default_config = super(config)
aws_config = AwsSecretsManager.database_config
DatabaseConfigurations::HashConfig.new(
default_config.env_name,
default_config.name,
default_config.configuration_hash.merge(aws_config)
)
end
end
end
config/initializers/aws_credentials.rb:
require './lib/active_record/aws_database_configurations'
ActiveRecord::DatabaseConfigurations.prepend(ActiveRecord::AwsDatabaseConfigurations)