ActiveStorageに`require "openssl"`を追加し、単体ロード時のNameErrorを修正
Active Storageを単独でrequireした際に発生していたNameError: uninitialized constant ActiveStorage::OpenSSLを、lib/active_storage.rbへのrequire "openssl"の追加で修正しました。
背景
このバグは、#54040でFIPS準拠対応として導入された checksum_implementation の設定コードが原因でした。lib/active_storage.rbのモジュールロード時にOpenSSL::Digest::MD5を参照しているにもかかわらず、opensslライブラリのrequireが行われていませんでした。
ほとんどのRailsアプリケーションでは、クレデンシャル処理やMessage Verifierなど他のコンポーネントがActive Storageより先にopensslをロードするため、この問題は顕在化しませんでした。しかし、#57098で報告されたように、require "active_storage"を直接実行するテストや最小構成のスクリプトでは、依存関係が暗黙的に解決されないためNameErrorが発生していました。
この種の「偶発的な依存関係」は、フレームワークのコンポーネントを単体で利用する場面や、ロード順序が変わった際に予期しない障害を引き起こします。
技術的な変更
activestorage/lib/active_storage.rbにrequire "openssl"を1行追加するのみの変更です。
変更前:
require "active_storage/errors"
require "marcel"
# :markup: markdown
変更後:
require "active_storage/errors"
require "marcel"
require "openssl"
# :markup: markdown
問題の根源は、checksum_implementationの初期化コードがモジュールのトップレベルでOpenSSL::Digest::MD5を参照していることにあります。
singleton_class.attr_accessor :checksum_implementation
@checksum_implementation = OpenSSL::Digest::MD5
begin
@checksum_implementation.hexdigest("test")
rescue # OpenSSL may have MD5 disabled
require "digest/md5"
@checksum_implementation = Digest::MD5
end
このコードはFIPS環境でMD5が無効化されている場合にDigest::MD5へフォールバックする設計ですが、その分岐に到達するより前にOpenSSL定数の解決が必要です。require "openssl"を追加することで、ロード順序に依存せず定数が確実に利用可能な状態になります。
設計判断
暗黙的な依存解決への依存を排除する方針が徹底されました。
Rubyではrequireによるロードは一度だけ実行されるため、opensslがすでに他でロード済みであれば追加のrequireは無操作になります。つまり、既存のRailsアプリケーションへの影響はゼロです。一方、単体ロード時には明示的なrequireによって依存関係が確実に満たされます。
修正の範囲を最小限(1行の追加)に留めていることも注目点です。checksum_implementationの初期化ロジックを変更するのではなく、依存ライブラリのロードを保証することで問題の本質に直接対処しています。
ライブラリ設計の観点では、外部定数への参照はそのライブラリのrequireと同一ファイルに明示的に記述するという原則の再確認といえます。
まとめ
本PRは1行の追加で、Active Storageの単体利用時の信頼性を回復させる修正です。require "openssl"の欠落という小さな見落としが、ロード順序という環境依存の要因によって長期間潜伏していた経緯は、OSSライブラリにおける明示的な依存宣言の重要性を改めて示しています。