📀

Active Storageで画像をパブリックにする方法

に公開

こんにちは!!
GWに有休を使って長期休みにしておらず、普通に働いています...(休みたかった
今回はActive Storageで画像をパブリックにする方法とどのように実装されているかについて記事にしました。
前提としては以下です。

  • Ruby : 3.4.X
  • Rails : 8.0.X
  • ストレージはAWSのS3
  • CDNで画像をキャッシュしたいため、画像をパブリックにしつつ生成されるURLを固定にしたい
  • すでにプライベートな画像をActive Storageを用いて保存している

Acitve Storageで画像をパブリックにする方法

まずはRailsガイドにもある通りサービスを設定します。

https://railsguides.jp/active_storage_overview.html#サービスを設定する

+ public:
+   service: S3
+   region: <%= ENV['AWS_REGION'] %>
+   bucket: <%= ENV['PUBLIC_BUCKET_NAME'] %>
+   public: true
+
private:
  service: S3
  region: <%= ENV['AWS_REGION'] %>
  bucket: <%= ENV['PRIVATE_BUCKET_NAME'] %>

public: true のサービスを分けることで利用するバケットを変え、プライベート用のS3のバケットとパブリック用のS3のバケットを分けます。

そしてActiveRecordのmodelで↑で追加したサービスを指定します。

class Hoge
-   has_one_attached :image
+   has_one_attached :image, service: :public
end

ちなみにパブリックにする画像は明示的に指定する状態が望ましいと思う場合はconfigファイルに private を指定しておくといいかもです!!
( has_one_attached でserviceを指定しなければ private が選択される

+     config.active_storage.service = :private

これでパブリックにできるため、URLが固定になります!!

実装見ていく

前提としてAWS S3を使っています。
以下のclassを用います。

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service/s3_service.rb#L8-L13

initialize を見ると以下のような記述があります。

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service/s3_service.rb#L25

public? は継承元で定義されており、yamlで指定しているものが入ってそうです。

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service.rb#L147-L149

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service.rb#L52-L54

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service/configurator.rb#L11-L20

では戻って @upload_options[:acl]public-read がついているとどうなるのか見ましょう。

ActiveStorage:Blobのインスタンスに upload 関数があります。

https://github.com/rails/rails/blob/3235827585d87661942c91bc81f64f56d710f0b2/activestorage/app/models/active_storage/blob.rb#L243-L246

https://github.com/rails/rails/blob/3235827585d87661942c91bc81f64f56d710f0b2/activestorage/app/models/active_storage/blob.rb#L255-L257

AcitveStorage::Blobのインスタンスに生えている service は先ほど見たS3Serviceです。

https://github.com/rails/rails/blob/3235827585d87661942c91bc81f64f56d710f0b2/activestorage/app/models/active_storage/blob.rb#L327-L329

そのS3Serviceの upload 関数は以下です。

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service/s3_service.rb#L28

**upload_option を渡していますね。

https://github.com/rails/rails/blob/6814f7130e0ea735c57ab848f403d1eca624c5bc/activestorage/lib/active_storage/service/s3_service.rb#L133-L134

object_for で返ってくるのはaws-sdk-rubyで宣言されているインスタンスです。

https://github.com/aws/aws-sdk-ruby/blob/4405132cdf9af8bfbeb1b7026f4e595e1c110873/gems/aws-sdk-s3/lib/aws-sdk-s3/bucket.rb#L1307-L1313

Aws::S3::Objectには put 関数が生えています。

https://github.com/aws/aws-sdk-ruby/blob/4405132cdf9af8bfbeb1b7026f4e595e1c110873/gems/aws-sdk-s3/lib/aws-sdk-s3/object.rb#L2995-L3004

pub_object 関数は以下です。

https://github.com/aws/aws-sdk-ruby/blob/4405132cdf9af8bfbeb1b7026f4e595e1c110873/gems/aws-sdk-s3/lib/aws-sdk-s3/client.rb#L17345-L17348

requestを送る際にoptionsを渡していることがわかります。
コメントアウトでも acl: "public-read" の説明があります。

https://github.com/aws/aws-sdk-ruby/blob/4405132cdf9af8bfbeb1b7026f4e595e1c110873/gems/aws-sdk-s3/lib/aws-sdk-s3/client.rb#L17276

AWSのドキュメントにも public-read の説明があり、readのみ全公開されていることが分かります。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/acl-overview.html#canned-acl

AWS S3にはアクセスの制御の仕方が複数あり、Active Storageはその中でもACLを使っていそうです。

https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/access-management.html#access-management-tools

S3のACLを使うならバケットポリシーなどで制限をかけたくなります。(ACLで検索すると非推奨という記事が散見される)
ここからは憶測ですが、Active StorageはS3のオブジェクトごとにアクセスをコントロールしたいためACLを使っているのかなと思います。

まとめ

Active Storageでパブリックアクセスを許可する方法とその実装を追ってみました。
AWS S3にはさまざまなアクセス制御の方法があり、Active Storageはその中でもACLを使って制御していることが分かりました。

以上です、よきRailsライフを..!!

SMARTCAMP Engineer Blog

Discussion