6
3

More than 3 years have passed since last update.

Rails アプリで S3 の権限制御を外さずに署名付きURLの署名を省いてファイルアクセスする方法を調べた

Last updated at Posted at 2020-03-13

はじめに

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 ルーティングにワイルドカードセグメントというものがあることを初めて知りました。

ただ、ワイルドカードで渡すとどんな文字列も渡せるようになるため、セキュリティを考慮してある程度のパラメータチェックは入れたほうが良いのかなと思います。

参考

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3