発生事象
複数のstg環境でActiveStorage#signed_idが同じものを生成したのでロジックを追ってみた
ActiveStorage#signed_idの生成を読解
signed_idはActiveStorageのBlobのid
を暗号化したもの
エントリーポイント
ActiveStorage::Blob#signed_id
がエントリーポイントです。
def signed_id(purpose: :blob_id, expires_in: nil, expires_at: nil)
super
end
superの実体
ActiveRecord::SignedId#signed_id
が呼ばれます。
def signed_id(expires_in: nil, expires_at: nil, purpose: nil)
raise ArgumentError, "Cannot get a signed_id for a new record" if new_record?
self.class.signed_id_verifier.generate id, expires_in: expires_in, expires_at: expires_at, purpose: self.class.combine_signed_id_purposes(purpose)
end
signed_id_verifier
利用されるMessageVerifierはActiveStorage.verifier
です。
(下位互換のためActiveRecord::SignedId.signed_id_verifier
ではなく上記を利用するようにoverrideしています)
def signed_id_verifier
@signed_id_verifier ||= ActiveStorage.verifier
end
ActiveStorage.verifier
rails/activestorage/lib/active_storage/engine.rbで設定されています。
initializer "active_storage.verifier" do
config.after_initialize do |app|
ActiveStorage.verifier = app.message_verifier("ActiveStorage")
end
end
app.message_verifier
rails/railties/lib/rails/application.rb
の
message_verifier
と
message_verifiers
def message_verifier(verifier_name)
message_verifiers[verifier_name]
end
def message_verifiers
@message_verifiers ||=
ActiveSupport::MessageVerifiers.new do |salt, secret_key_base: self.secret_key_base|
key_generator(secret_key_base).generate_key(salt)
end.rotate_defaults
end
ActiveSupport::MessageVerifiers
ActiveSupport::MessageVerifiersへのアクセスするキーはsaltとして扱われます
def [](salt)
@codecs[salt] ||= build_with_rotations(salt)
end
ActiveSupport::MessageVerifiersの各verifierの生成
def build_with_rotations(salt)
rotate_options = @rotate_options.map { |options| options.is_a?(Proc) ? options.(salt) : options }
transitional = self.transitional && rotate_options.first
rotate_options.compact!
rotate_options[0..1] = rotate_options[0..1].reverse if transitional
rotate_options = rotate_options.map { |options| normalize_options(options) }.uniq
raise "No options have been configured for #{salt}" if rotate_options.empty?
rotate_options.map { |options| build(salt.to_s, **options) }.reduce(&:fall_back_to)
end
def build(salt, secret_generator:, secret_generator_options:, **options)
raise NotImplementedError
end
buildの実体
rails/activesupport/lib/active_support/message_verifiers.rb
で build
を上書きしています
def build(salt, secret_generator:, secret_generator_options:, **options)
MessageVerifier.new(secret_generator.call(salt, **secret_generator_options), **options)
end
secret_generator
は上記message_verifiers
の生成ロジックで指定しているため、key_generator(secret_key_base).generate_key(salt)
で生成されます
secret_key_base
暗号化キーを生成するためのキーは
rails/railties/lib/rails/application.rb
のsecret_key_base
def secret_key_base
if Rails.env.local? || ENV["SECRET_KEY_BASE_DUMMY"]
config.secret_key_base ||= generate_local_secret
else
validate_secret_key_base(
ENV["SECRET_KEY_BASE"] || credentials.secret_key_base
)
end
end
結論
ENV["SECRET_KEY_BASE"] || credentials.secret_key_base
が環境間で同じ値であれば、同じidに対して同じActiveStorage::Blob#signed_id
が生成される
再現
signed_id_verifierの生成
secret=Rails.application.key_generator.generate_key("ActiveStorage")
signed_id_verifier=ActiveSupport::MessageVerifier.new secret
signed_idの生成
id = {ActiveStorage::Blob.id}
signed_id = signed_id_verifier.generate(id, purpose: :blob_id)
signed_idの検証
signed_id_verifier.verify(signed_id, purpose: :blob_id)
# => {ActiveStorage::Blob.id}