fog-aws + localstack を使ってローカル環境で S3 の署名付き URL を発行する
この記事では、ローカル開発環境でAWSのS3を模倣できるLocalStackと、Railsのgemであるfog-awsを使用して、S3の署名付きURLを発行する方法を解説します。
環境設定
バージョンは以下の通り
- Ruby 3.3.4
- Rails 7.1.4
- fog-aws 3.24.0
- localstack 3.7.0
Rails 環境のセットアップ等は割愛します。
Compose.yml
まず、compose.ymlファイルを以下のように設定します。
version: '3.8'
services:
# dbの設定は省略
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails server -b 0.0.0.0"
volumes:
- .:/myapp
ports:
- "3000:3000"
environment:
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
AWS_DEFAULT_REGION: ap-northeast-1
BUCKET_NAME: rails-playground # init.shで指定するバケット名と同一にする
LOCALSTACK_ENDPOINT: https://localhost.localstack.cloud:4566
depends_on:
- db
- localstack
networks:
- app-network
localstack:
image: localstack/localstack:3.7.0
ports:
- "4566:4566"
environment:
DEBUG: 1
AWS_ACCESS_KEY_ID: test # S3_SKIP_SIGNATURE_VALIDATIONを0に設定することでtestが固定値になる
AWS_SECRET_ACCESS_KEY: test # 同上
AWS_DEFAULT_REGION: ap-northeast-1
SERVICES: s3
S3_SKIP_SIGNATURE_VALIDATION: 0 # 正しい署名付きURLのみでファイルアクセスを許可
volumes:
- localstack_volume:/etc/localstack
- ./init.sh:/etc/localstack/init/ready.d/init.sh # 起動時にシェルスクリプトを実行
networks:
app-network:
aliases:
- localhost.localstack.cloud
networks:
app-network:
volumes:
db_data:
localstack_volume:
ここで重要なのは、LocalStackのネットワーク設定です。
rails アプリケーションも docker 環境で立ち上げているのであればコンテナ間通信時には https://localhost.localstack.cloud:4566 でアクセスできるようにしなければなりません。
しかしコンテナ間通信はデフォルトではホストは localstack となってしまいます。そこでホストが localhost.localstack.cloud でも通信できるようあらかじめエイリアス機能を利用しておきます。
networks:
app-network:
aliases:
- localhost.localstack.cloud
この設定によりhttps://localhost.localstack.cloud:4566
でコンテナ間通信が可能になります。
S3バケットの作成
LocalStackでは、awslocal
というAWS CLIのラッパーが提供されています。これを使用することで、エンドポイントの指定などを省略できます。
shellスクリプト(init.sh)を作成し、LocalStackの起動時に実行するよう設定します。
#!/bin/bash
awslocal s3 mb s3://rails-playground
コンテナ起動時の hooks 機能が使えるのでここでバケットを作成しておきます。
volumes:
- localstack_volume:/etc/localstack
- ./init.sh:/etc/localstack/init/ready.d/init.sh # コンテナ起動時にシェルスクリプトを実行
fog-awsを使用した画像アップロード
fog-awsを使用して、S3に画像をアップロードする方法は以下の通り。
あらかじめ public 配下に置いてある画像ファイルを s3 上にアップロードしておきます。
fog_storage = Fog::Storage.new(
provider: 'AWS',
region: ENV['AWS_DEFAULT_REGION'],
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
endpoint: ENV['LOCALSTACK_ENDPOINT'],
path_style: true
)
image_path = Rails.public_path.join('test.png')
content = File.read(image_path)
fog_storage.directories.new(key: ENV['BUCKET_NAME']).files.create(key: 'file_name', body: content, content_type: 'image/png')
署名付きURLの生成
アップロードした画像の署名付きURLを生成するには、以下のコードを使用します。ローカル開発環境では、get_object_url
メソッドを使用しないとポート番号が省略されてしまうため、注意が必要です。
fog_storage = Fog::Storage.new(
provider: 'AWS',
region: ENV['AWS_DEFAULT_REGION'],
aws_access_key_id: ENV['AWS_ACCESS_KEY_ID'],
aws_secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
endpoint: ENV['LOCALSTACK_ENDPOINT'],
path_style: true
)
fog_storage.get_object_url(ENV['BUCKET_NAME'], 'file_name', Time.current.to_i + 3600) # 1時間後に失効
このコードを実行すると、以下のような署名付きURLが生成されます:
https://localhost.localstack.cloud:4566/rails-playground/file_name?X-Amz-Expires=300&X-Amz-Date=20240830T162354Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test%2F20240830%2Fap-northeast-1%2Fs3%2Faws4_request&X-Amz-SignedHeaders=host&X-Amz-Signature=7c5d408e52eefe467a8f9a2c0556df781cc527aaaf9a82133636130d8bb4b3e4
動作確認
生成されたURLをブラウザで開くと、アップロードした画像が表示されます。
一方、不正なURLでアクセスしようとすると、アクセスが拒否されます。
これにより、セキュリティが正しく機能していることが確認できます。
Discussion