はじめに
Rails アプリを用いて、S3 の権限制御を外さずに署名付きURLを省いてアクセスする方法について調べたので、その方法をまとめておきます。
背景
S3 バケット内のファイルに権限制御を入れ、そのファイルに対してアクセスするとき、署名付きURLを用いることがあると思います。こんなURLですね。(クレデンシャルはマスクしています)
https://example.s3-ap-northeast-1.amazonaws.com/foo/bar/piyo.txt/?X-Amz-Expires=-0000000000&X-Amz-Date=0000000000000000&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AAAAAAAAAAAAAAAAAAAA/00000000/ap-northeast-1/s3/aws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=0000000000000000000000000000000000000000000000000000000000000000
しかしURLについた署名が、外部ライブラリを使っているときにノイズになることがあります。
例えば本のビューアーや写真のアルバムのようなライブラリを Rails アプリ上で扱っていると、ライブラリでは画像のファイル群をファイル指定で逐一アクセスするというような処理が走ることがあります。
おおよそそのような外部ライブラリは、アプリの同サーバ上のパブリックにアクセスできるディレクトリに画像ファイル群がおかれ、そこに対してアクセスするというケースを想定して作られていることが多いと思います。外部ライブラリ側では画像ファイルについた署名を考慮せずアクセスし、想定通り動かない、ということが起きがちです。
そのため、権限制御を入れたままで、署名付きURLを省いた URL で S3 バケット内のファイルにアクセスできるようにできないかを調べていました。
どうやったか
Rails アプリのルーティングで、S3 バケットのファイルパスをある種プロキシし、実際の S3 バケットの署名付きURLにリダイレクトするみたいな仕組みにしてみました。
以下、やりかたを簡単にまとめます。
ユースケース
以下のファイルに、署名付きURLなしでリクエストしたいとします。
- バケットURL:
https://example.s3-ap-northeast-1.amazonaws.com
- ファイルパス:
foo/bar/piyo.txt
イメージとしては、Rails の URL を https://rails-sample.com
としたら、以下URLで S3 ファイルにアクセスできるようにしたいです。
https://rails-sample.com/remote_storages/proxy/foo/bar/piyo.txt
ワイルドカードセグメントを使った Rails のルーティングを作る
まず、ルーティング用のコントローラーを作りますが、その時のルーティング設定では、ワイルドカードセグメントを使います。
ワイルドカードセグメントとは、最初にアスタリスク(*
) がついた部分のパラメータのことで、ルーティングのある位置から下のすべての部分にパラメータを展開させるために利用できます。
例えば RemoteStoragesController
というコントローラーで#proxy
という get メソッドを作るとしたら、以下のようになります。
resources :remote_storages, only: [] do
collection do
get 'proxy/*path', to: 'remote_storages#proxy', as: 'proxy'
end
end
上記により、path
には foo/bar/piyo.txt
というようなスラッシュ有りのパラメータを渡すことができるようになります。
コントローラー作成
次にコントローラーを作ります。以下では S3 へのアクセスは fog を使っていますが、AWS SDK を使っても良いと思います。
以下では proxy
メソッドに渡ってきた path
と拡張子 format
に応じて、署名付きURLにリダイレクトするという処理を作っています。
検証してみてわかったのですが、ワイルドカードセグメントには .txt
のような拡張子は渡ってきませんでした。代わりに、format
というパラメータに txt
という文字列が入ってくるため、メソッド内で再度ファイルパスを作り直しています。
また、アクセスキーやバケットは Rails.application.secrets
で秘匿化すると良さそうです。
class RemoteStoragesController < ApplicationController
def proxy
s3_bucket = Fog::Storage.new(
provider: 'AWS',
aws_access_key_id: 'xxx',
aws_secret_access_key: 'xxx',
region: 'ap-northeast-1'
).directories.get('example')
# 拡張子は *path に入らず :format に入るためここで調整している
path = "#{params[:path]}.#{params[:format]}"
redirect_to s3_bucket.files.get_https_url(path, 1.minutes.since.to_i)
end
end
これによって、以下にアクセスすると、
https://rails-sample.com/remote_storages/proxy/foo/bar/piyo.txt
以下ファイルパスの署名付きURLにリダイレクトするということができます。
https://example.s3-ap-northeast-1.amazonaws.com/foo/bar/piyo.txt
おわりに
今回 Rails ルーティングにワイルドカードセグメントというものがあることを初めて知りました。
ただ、ワイルドカードで渡すとどんな文字列も渡せるようになるため、セキュリティを考慮してある程度のパラメータチェックは入れたほうが良いのかなと思います。