📷

fog-aws + localstack を使ってローカル環境で S3 の署名付き URL を発行する

2024/08/31に公開

この記事では、ローカル開発環境で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ファイルを以下のように設定します。

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の起動時に実行するよう設定します。

init.sh
#!/bin/bash
awslocal s3 mb s3://rails-playground

コンテナ起動時の hooks 機能が使えるのでここでバケットを作成しておきます。
https://docs.localstack.cloud/references/init-hooks/

compose.yml
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をブラウザで開くと、アップロードした画像が表示されます。

image1

一方、不正なURLでアクセスしようとすると、アクセスが拒否されます。

image2

これにより、セキュリティが正しく機能していることが確認できます。

参考

Discussion