📌

carrierwave + fogで非公開でS3にアップロードした一部画像をCloudFrontで公開する

2021/11/14に公開

背景

carrierwavefogを非公開で使ってAmazon S3にアップロード。よくやるやつです。
(非公開というのは fog_public = false でできます) ref. Using Amazon S3

このファイルをpublicにアクセスできるようにしたいってありますよね。

それをやる方法のメモです。

概要

Amazon CloudFrontを使います。
CloudFrontでS3バケットのdistributionを作り、
S3にのバケットに CloudFrontのdistributionへのアクセスを許可すればOKです。
さらに、オリジンパスをつかえば、s3バケットの中の特定のキー配下のものだけを公開することができます。

方法

  1. S3全体をCloudFrontを使って公開する
  2. S3の一部をCloudFrontを使って公開する

S3全体をCloudFrontを使って公開する

  • S3バケットを作成する
  • carrierwaveの関連のコードを書く
  • cloudfrontを設定する
  • S3バケットポリシーを確認する
  • 動作確認

s3バケット作成する

特に説明することないです。ふつうに作ります

ポイントは、なにもしないことパブリックアクセスをすべて ブロックです。 https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/userguide/access-control-block-public-access.html

carrierwaveでs3にアップロードできるようにする

これも普通です。 非公開にするself.fog_public = falseを除いて。

参考 Fog Using Amazon S3
public_imageというstringのカラムを持つRabbitモデルを仮定します。

まずは、migration。

class CreateRabbits < ActiveRecord::Migration[6.1]
  def change
    create_table :rabbits do |t|
      t.string :public_image, comment: '公開'
      t.timestamps
    end
  end
end

uploaderを作る。

まずはBaseS3Uploaderを作る。 ( Configuring CarrierWave のように initialization配下で設定するでもよいです。)

require 'fog/aws'

class BaseS3Uploader < CarrierWave::Uploader::Base
  storage :fog

  def initialize(*)
    super

    self.fog_credentials = {
      provider: 'AWS',
      aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
      aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
      region: ENV['AWS_S3_BUCKET_REGION']
    }
    self.fog_directory = ENV['AWS_S3_BUCKET_NAME']
    self.fog_public = false
  end

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
  end

  def extension_allowlist
    %w[jpg jpeg gif png]
  end

  def filename
    original_filename
  end
end

baseを継承して実際に使うPublicUploaderを作る。

class PublicUploader < BaseS3Uploader
  storage :fog
end

PublicUploaderをpublic_imageにセットする。

class Rabbit < ApplicationRecord
  mount_uploader :public_image, PublicUploader
end

cloudfrontを設定する

追加したs3バケットをオリジンドメインをとするdistributionを作成します。

S3バケットポリシーを確認する

アクセス許可タブを押して、「バケットポリシー」が以下の記述が追加されている事を確認してください。


carrierwaveのuploaderを修正する

上記で作ってcloudfrontをPublicUploaderに設定します。

asset_hostにディストリビューションドメイン名を追加します。

また、このままだとurl methodでcloudfrontを見てくれません。
しかたないので、urlをoverlideします。参考 CarrierWave+FogでリソースをS3に保存・CloudFrontで配信するときの小ネタ

最終的にはこんな感じにしました。

class PublicUploader < BaseS3Uploader
  storage :fog

  def initialize(*)
    super
    self.asset_host = 'https://d37z6zt9zkkh1k.cloudfront.net' # これを追加する
  end

  # これを追加する
  def url(*args)
    "#{asset_host}/#{store_dir}/#{identifier}"
  end
end

動作確認する

tmpフォルダにhoge.pngという画像があるとします。

その画像をs3にアップロードします。

file = File.open('tmp/hoge.png')
Rabbit.create!(public_image: file)

urlを取得すると、cloudfrontのurlになっている。
urlにアクセスして画像が表示されれば完了です。

Rabbit.public_image.url
#=> "https://d37z6zt9zkkh1k.cloudfront.net/uploads/rabbit/public_image/1/hoge.png"

S3の一部をCloudFrontを使って公開する

S3全体をCloudFrontを使って公開するをまずぜんぶやります。そこから出発です。

  • cloudfrontを修正する
  • S3バケットポリシーを修正する
  • uploaderを修正する

cloudfrontを修正する

最初の設定ではS3のバケット全てが対象でした。

これだと秘匿にしたい情報を公開されてしまう可能性があります。

なので、特定のファイル(object)のみになるように変更します。

参考 Amazon CloudFront オリジンのパス

具体的には、/uploads/rabbit/public_imageという記述を追加します。

S3バケットポリシーを修正する

公開するオブジェクトを限定します。

参考 Amazon Simple Storage Service (S3)
追加条件付きでの複数のアカウントへのアクセス許可の付与

junara-rails-app-bucket/uploads/rabbit/public_image/* というふうに記述を変更しました。
これにより、cloudfrontからのアクセスが制限されます。

uploaderを修正する

#{store_dir}は、cloudfrontに含まれます。url methodのみを若干修正します。

  # これを追加する
  def url(*args)
    "#{asset_host}/#{identifier}"
  end

動作確認する

s3全体をCloudFrontを使って公開するとおなじ動作確認ができればOKです。

Discussion