[rails/rails] ActiveStorageのチェックサム計算責務をServiceクラスに移譲

rails/rails

背景

これまでActiveStorageでは、ファイルの整合性チェックに使用するチェックサムの計算が複数のクラスに分散していました。具体的にはActiveStorage::Blobクラスが内部でOpenSSL::Digest::MD5を直接使用してチェックサムを計算し、ActiveStorage::Downloaderクラスが検証を担当するという構造でした。

しかし、S3、GCS、Azureといった主要なクラウドストレージサービスは、MD5以外にもSHA256などの複数のチェックサムアルゴリズムをサポートしています。特にFIPS準拠が求められる環境ではMD5が使用できないため、より柔軟なアルゴリズム選択が必要とされていました。

#54468では、チェックサムの計算と検証の責務を完全にActiveStorage::Serviceクラスに集約することで、各ストレージサービスが独自のアルゴリズムを実装できるようにしました。

主な変更内容

1. Serviceクラスへのチェックサム計算メソッドの追加

ActiveStorage::Service基底クラスに、チェックサム計算を担当する2つの新しいメソッドが追加されました。

def compute_checksum(io, **options)
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)

  # Fileオブジェクトの場合はDigestクラスのfile実装を使用
  return checksum_implementation(**options).file(io).base64digest if io.is_a?(File)
  return checksum_implementation(**options).base64digest(io.read) if default_chunk_size.to_i == 0

  checksum_implementation(**options).new.tap do |checksum|
    read_buffer = "".b
    while io.read(default_chunk_size, read_buffer)
      checksum << read_buffer
    end

    io.rewind
  end.base64digest
end

def checksum_implementation(**)
  OpenSSL::Digest::MD5
end

compute_checksumメソッドは、IOオブジェクトからチャンク単位でデータを読み込みながらチェックサムを計算します。デフォルトのチャンクサイズは5MBです。checksum_implementationメソッドは使用するダイジェストクラスを返し、デフォルトではMD5を使用します。

2. S3ServiceでのSHA256サポート

S3Serviceクラスでは、初期化時にdefault_digest_typeオプションで使用するアルゴリズムを指定できるようになりました。

def initialize(bucket:, upload: {}, public: false, default_digest_type: :md5, **options)
  @client = Aws::S3::Resource.new(**options)
  @transfer_manager = Aws::S3::TransferManager.new(client: @client.client) if defined?(Aws::S3::TransferManager)
  @bucket = @client.bucket(bucket)

  @default_digest_type = default_digest_type.downcase.to_sym
  @default_digest_class = digest_to_class(@default_digest_type)
  # ...
end

def compute_checksum(file, **options)
  default_digest_type == :md5 ? super : "#{default_digest_type}:#{super}"
end

SHA256を使用する場合、チェックサムの形式は"sha256:base64digest"となり、プレフィックスでアルゴリズムを識別できます。

設定ファイルでの指定例:

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: your_bucket
  default_digest_type: :sha256

3. ダイレクトアップロードのアルゴリズム対応

クライアント側のJavaScriptでもSHA256をサポートするため、新しいアルゴリズムモジュールが追加されました。

import { sha256 } from "js-sha256"

function hexToBase64(hexString) {
  const bytes = []
  for (let i = 0; i < hexString.length; i += 2) {
    bytes.push(parseInt(hexString.substring(i, i + 2), 16))
  }

  let binary = ""
  for (let i = 0; i < bytes.length; i++) {
    binary += String.fromCharCode(bytes[i])
  }

  return btoa(binary)
}

export const sha256Algorithm = {
  createBuffer: () => sha256.create(),
  append: (buffer, data) => buffer.update(data),
  getChecksum: (buffer) => {
    const hexDigest = buffer.hex()
    return `sha256:${hexToBase64(hexDigest)}`
  }
}

フォームヘルパーでアルゴリズムを指定:

<%= form.file_field :attachment, 
    direct_upload: true, 
    data_checksum_algorithm: "sha256" %>

生成されるHTML:

<input data-direct-upload-url="http://example.com/rails/active_storage/direct_uploads" 
       data-checksum-algorithm="sha256" 
       type="file" 
       name="post[attachment]" 
       id="post_attachment" />

4. Downloaderクラスの削除とServiceへの統合

ActiveStorage::Downloaderクラスが削除され、その機能がService基底クラスのopenメソッドに統合されました。これにより、ダウンロードと検証のロジックが各サービスで制御できるようになりました。

変更前:

ActiveStorage::Downloader.new(service).open(key, checksum: checksum)

変更後:

service.open(key, checksum: checksum)

内部的にはdownload_and_verify_tempfileプライベートメソッドが処理を担当し、サービスのcompute_checksumメソッドを使用して検証を行います。

5. Blobクラスからチェックサム計算ロジックの削除

ActiveStorage::Blobクラスからcompute_checksum_in_chunksプライベートメソッドが削除され、サービスのcompute_checksumメソッドを呼び出すようになりました。

def unfurl(io, identify: true)
  self.checksum = service&.compute_checksum(io)
  self.content_type = extract_content_type(io) if content_type.nil? || identify
  self.byte_size = io.size
  self.identified = true
end

技術的な影響

後方互換性

  • デフォルトではMD5を使用するため、既存のアプリケーションへの影響はありません
  • S3Serviceでdefault_digest_type: :sha256を指定した場合のみSHA256が使用されます
  • 既存のMD5チェックサムを持つBlobは引き続き正常に動作します

パフォーマンス

  • チャンクサイズは5MBに設定されており、メモリ効率的にチェックサムを計算できます
  • Fileオブジェクトの場合はDigest.file()メソッドを使用し、最適化されたパスを利用します

セキュリティ

  • FIPS準拠環境でSHA256を使用できるようになりました
  • 各クラウドサービスが推奨するアルゴリズムを選択可能になりました

まとめ

この変更により、ActiveStorageのチェックサム計算の責務が完全にServiceクラスに集約されました。各ストレージサービスが独自のアルゴリズムを実装でき、MD5以外の選択肢が提供されるようになりました。特にS3ServiceでのSHA256サポートは、セキュリティ要件が厳格な環境での利用を可能にする重要な機能追加です。

この変更は、将来的にGCSやAzureサービスでも同様のアルゴリズム選択をサポートするための基盤となります。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ✓ PASS

記事構成とDiffDaily Styleへの準拠状況

記事構成の3要素(Title, Context, Technical Detail)が明確に記載されています。コードブロック前後の空行やファイル名付きシンタックスハイライト、GitHubリンク記法など、すべてのカスタムMarkdown構文が正しく使用されており、可読性が非常に高いです。対象読者であるエンジニアに適した技術レベルで書かれています。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

記事で引用されているコードスニペットと技術的な説明は、PRの目的(Serviceクラスへの責務集約)と完全に一致しています。`compute_checksum`の追加、`S3Service`の拡張、`Downloader`の削除といった主要な変更点が、技術的に正確かつ分かりやすく解説されています。用語の誤用も見られません。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

PRのタイトル「Make ActiveStorage::Service responsible for checksums」の内容を正確に反映しており、ハルシネーションは見られません。背景(FIPS準拠の必要性)、具体的な変更内容、技術的影響のすべてが、PRの情報と完全に整合性が取れています。PR番号も正確です。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除