carrierwave + fogで非公開でS3にアップロードした一部画像をCloudFrontで公開する
背景
carrierwaveとfogを非公開で使ってAmazon S3にアップロード。よくやるやつです。
(非公開というのは fog_public = false
でできます) ref. Using Amazon S3
このファイルをpublicにアクセスできるようにしたいってありますよね。
それをやる方法のメモです。
概要
Amazon CloudFrontを使います。
CloudFrontでS3バケットのdistributionを作り、
S3にのバケットに CloudFrontのdistributionへのアクセスを許可すればOKです。
さらに、オリジンパス
をつかえば、s3バケットの中の特定のキー配下のものだけを公開することができます。
方法
- S3全体をCloudFrontを使って公開する
- 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)のみになるように変更します。
具体的には、/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