記事概要
Ruby on RailsにAWSのS3を実装する方法について、まとめる
前提
- Macを使用している
- Renderにデプロイしている
- Ruby on Railsでアプリケーションを作成している
- AWSのアカウントを保持している
- AWS CloudShellを使用する
- Cloud Formationだと、コードでインフラ環境を構築・管理・運用まで行うことができる
- ひとつひとつのコードの意味を理解しながら順番に実行するためにこちらを採用
S3(Amazon Simple Storage Service)とは
AWSが提供するサービスの一つであり、画像を保存したり、保存してある画像を取得したりできる。インターネット上にデータを保存する箱を借りられるサービスであるとイメージをすると良い
S3に保存されたデータは実在の施設に分散して保管されるため、ネットワーク障害に強い仕様。また、使用した分だけ課金されるシステムであるため、保存できるデータ容量やファイル数の上限がない
無料使用期間
AWSには「12ヶ月間、一定の使用量までは様々なサービスを無料で利用できる」という無料枠が用意されている
サービス名 | 無料枠 |
---|---|
S3 | ・5GBの容量 ・20,000 件のGetリクエスト ・2,000 件のPutリクエスト |
無料枠でS3を利用する際は以下に注意する
- 不要となったらS3の利用を解除する
- 「バケット」と呼ばれる画像データを保存する箱は、不要になったら削除する
- 必要のない「バケット」は作成しないよう注意する
- 費用はこまめに確認する
- AWSの請求ダッシュボードから、いつでも確認できる
導入の流れ
セキュリティ対策
S3を利用する際にも、セキュリティ対策が必要
理由:S3を利用する際に必要となるアクセスキーという秘密情報がGitHubで公開されてしまった場合、不正利用の被害を受ける危険性があるため
- AWS上でのセキュリティ対策
- 保存機能にアクセス制限を設定(バケットポリシー )
- GitHubでのソースコード管理におけるセキュリティ対策
- 秘密情報を変数に代入して使用(環境変数)
- 秘密情報を誤操作でコミットしないよう防止する機能(git-secrets)
Cloud Formation
AWSのIaCとして提供されており、コードでインフラ環境を構築・管理・運用まで行うことができる
AWS CloudShell
AWSのブラウザ上でAWSのサービスを操作できるCLI
AWS CloudShellを使えば、手元のPC環境にかかわらずブラウザ上からコマンドでAWSの操作を行うことができる
時間制限
CloudShellは20分間キーボードやマウスを触らないと、以下のようなメッセージが出ることがある。画面をクリックすると再び復旧する
Connection is lost. Please refresh the browser to re-establish the connection.
コードをコピー
コードを貼り付けると、注意画面が表示される
コピーしたコードが表示されているのか確認できたら、「貼り付け」をクリックし、エンターでコードを実行する
用語
バケット
S3では、データを保存する場所のことを「バケット」と呼ぶ。パソコンのフォルダのようなイメージ
リージョン
S3に保存されたデータを保管している、実在の施設が所在する場所
日本には、「アジアパシフィック(東京)」と「アジアパシフィック(大阪)」の2つのリージョンがある
オブジェクト
S3では、バケット内にアップロードするファイルのことを「オブジェクト」と呼ぶ
バケットの暗号化
S3のストレージに保存されるデータを保護するための仕組み
- クライアントサイド暗号化
ユーザーが暗号化してからAWSにアップロードする方法
ユーザーは暗号化キーを大切に管理して、データの暗号化と暗号化解除を行う - サーバーサイド暗号化
AWSにアップロードするときに暗号化する方法- S3に初期設定されているバケットの暗号化には、
SSE-S3(Server-Side Encryption with Amazon S3-Managed Keys)
というサーバーサイド暗号化という手法が使われている - データをサーバーに保存する前に暗号化する手法で、この暗号化によってデータを不正アクセスや盗難から保護することができる
- S3が暗号化キーを自動的に管理しているので、ユーザーが暗号化キーを管理しなくても、Amazon S3がデータの暗号化と暗号化解除を行う
- ユーザーはデータのセキュリティを保つために必要な操作をAmazon S3に任せることができ、安全で簡単に暗号化を行うことができる
- S3に初期設定されているバケットの暗号化には、
バケットポリシー
バケットに対して、どのようなユーザーがどのような処理をできるか取り決めをするもの。処理とは、「一覧表示」「読み取り」「書き込み」「削除」
ツール
git-secrets
AWSが公開しているツールであり、commitしようとしたコードをチェックし、パスワードだと推定されるような文字列が含まれている場合は、警告を出して処理が中断してくれる機能
コマンド
aws s3 mb コマンド
S3のバケットを作成するコマンド
$ aws s3 mb s3://バケット名
バケットの名前はアクセスするときのURLに使用されるため、「英数字 かつ 誰も付けたことがない名前」を使う必要がある。名前が重複してしまう場合は、「付けたい名前 + 数値」を試してみるのがオススメ。
aws s3 lsコマンド
S3のバケット一覧を表示するコマンド
$ aws s3 ls
#=> 2023-10-16 06:37:24 bucket100200400
aws s3api get-bucket-ownership-controlsコマンド
S3のバケット内のオブジェクトの所有者を表示するコマンド
$ aws s3api get-bucket-ownership-controls --bucket バケット名
aws s3api get-bucket-encryption コマンド
S3のバケットに現在設定されているバケットの暗号化の設定を確認するコマンド
$ aws s3api get-bucket-encryption --bucket バケット名
aws s3api get-public-access-blockコマンド
S3のバケットに現在設定されているアクセス許可を確認するコマンド
デフォルトだと、外部からのアクセスに対して全てブロックする
$ aws s3api get-public-access-block --bucket バケット名
aws s3api put-public-access-blockコマンド
S3のバケットに現在設定されているアクセス許可を変更するコマンド
$ aws s3api put-public-access-block --bucket バケット名 --public-access-block-configuration '
{
"BlockPublicAcls": true,
"IgnorePublicAcls": true,
"BlockPublicPolicy": false,
"RestrictPublicBuckets": false
}'
aws iam list-usersコマンド
AWSアカウントのIAMユーザー一覧を取得する
$ aws iam list-users
aws s3api put-bucket-policy コマンド
S3バケットにポリシーを設定する
aws s3api put-bucket-policy --bucket ② --policy "$(cat << EOF
{
"Version": "2012-10-17",
"Id": "Policy1544152951996",
"Statement": [
{
"Sid": "Stmt1544152948221",
"Effect": "Allow",
"Principal": {
"AWS": "①"
},
"Action": "s3:*",
"Resource": "arn:aws:s3:::②"
}
]
}
EOF
)"
- ①:「Arn」の情報を入力する
- ②:バケット名を入力する
aws s3api get-bucket-policy コマンド
S3バケットのバケットポリシーを取得するコマンド
$ aws s3api get-bucket-policy --bucket バケット名
手順1(AWS CloudShellを開く)
- AWSにルートユーザーとしてログインする
- 画面上部の検索フォームに「cloudshell」と入力し、検索結果からColudShellのページにアクセスする
- CloudShellが起動し、CLIの画面が表示される
手順2(S3で画像の保存先を作成する)
手順2-1(バケットを作成)
-
ブラウザ右上のリージョンが「アジアパシフィック(東京)」になっていることを確認する
-
CloudShellで下記を実行し、バケットを作成する
$ aws s3 mb s3://furima41468
-
実行結果を確認する
$ make_bucket: furima41468
- 失敗ケース1
他の人がすでにそのバケット名を使っている場合のエラー$ make_bucket failed: s3://バケット名 An error occurred (BucketAlreadyExists) when calling the CreateBucket operation: The requested bucket name is not available. The bucket namespace is shared by all users of the system. Please select a different name and try again.
- 失敗ケース2
すでに自身が作成したバケット名と同じ名前を使っている場合のエラー$ make_bucket failed: s3://バケット名 An error occurred (BucketAlreadyOwnedByYou) when calling the CreateBucket operation: Your previous request to create the named bucket succeeded and you already own it.
- 失敗ケース1
-
バケットが作成できたか確認する
$ aws s3 ls #=> 2023-10-16 06:37:24 furima41468
手順2-2(所有者を確認)
- S3のバケット内のオブジェクトの所有者を表示する
$ aws s3api get-bucket-ownership-controls --bucket furima41468
- 実行結果を確認する
{ "OwnershipControls": { "Rules": [ { "ObjectOwnership": "BucketOwnerEnforced" } ] } }
- OwnershipControls
- 所有権のコントロールに関する設定
- Rules
- 所有権のコントロールに関する設定のルール
- ObjectOwnership
- オブジェクトの所有権に関するルール
- BucketOwnerEnforced
- オブジェクトの所有者をバケットの所有者に適用すること
- この設定はバケット内で新しく作成されるすべてのオブジェクトの所有者は、バケットの所有者とし、これが強制的に適用される
- 例
Aさんのバケットにアクセスしてオブジェクトを追加しようとするBさんがいた場合、新しくAさんのバケットへ追加されるオブジェクトの所有者はBさんではなく、バケットの所有者のAさんのオブジェクトとして適用される。
これにより、バケットを所有するAさんはバケット内にあるオブジェクトの所有者となり、オブジェクトの管理がしやすくなる。
手順2-3(バケットの暗号化を確認)
- S3のバケットに現在設定されているバケットの暗号化の設定を確認する
$ aws s3api get-bucket-encryption --bucket furima41468
- バケットに新たにアップロードされるすべてのオブジェクトに、サーバーサイド暗号化が適用されている場合、下記が表示される
{ "ServerSideEncryptionConfiguration": { "Rules": [ { "ApplyServerSideEncryptionByDefault": { "SSEAlgorithm": "AES256" }, "BucketKeyEnabled": false } ] } }
手順2-4(アクセス許可を設定)
-
S3のアクセス許可とは
S3のバケットやオブジェクトへのアクセスを許可する範囲を決めること
アクセスを許可する範囲を決めることで、セキュリティーを保ちながらS3へアクセスすることができる具体的には、ユーザーがバケットやオブジェクトに対して、一覧表示、読み取り、書き込み、削除ができる範囲の設定する
- S3のバケットに現在設定されているアクセス許可を確認する
$ aws s3api get-public-access-block --bucket furima41468
- 外部からのアクセスに対して全てブロックするデフォルトの設定になっていることを確認する
※現在のアクセス許可の設定は、S3への全てのアクセスがブロックされ、外部からファイルがアップロードできない
{ "PublicAccessBlockConfiguration": { "BlockPublicAcls": true, "IgnorePublicAcls": true, "BlockPublicPolicy": true, "RestrictPublicBuckets": true } }
- 外部からS3へファイルがアップロードできるように、アクセス許可を変更する
$ aws s3api put-public-access-block --bucket furima41468 --public-access-block-configuration ' { "BlockPublicAcls": true, "IgnorePublicAcls": true, "BlockPublicPolicy": false, "RestrictPublicBuckets": false }'
- 設定変更ができていることを確認する
$ aws s3api get-public-access-block --bucket furima41468
- 外部からファイルアップロードできる設定になっていることを確認する
{ "PublicAccessBlockConfiguration": { "BlockPublicAcls": true, "IgnorePublicAcls": true, "BlockPublicPolicy": false, "RestrictPublicBuckets": false } }
手順2-5(バケットポリシーを設定)
-
AWSアカウントのIAMユーザー一覧を取得する
$ aws iam list-users
-
一覧を確認し、IAMユーザーの個別情報である「Arn」を保存する
{ "Users": [ { "Path": "/", "UserName": "ユーザー名", "UserId": "ユーザーID", "Arn": "arn:aws:iam::263931220713:user/ユーザー名", "CreateDate": "IAMユーザーの作成日時" } ] } #=> "Arn": "arn:aws:iam::263931220713:user/ユーザー名" を保存
-
VSCodeを開く
-
ホームディレクトリ/s3/policy.json
ファイルを手動作成する -
policy.jsonに下記を記述する
policy.jsonaws s3api put-bucket-policy --bucket ② --policy "$(cat << EOF { "Version": "2012-10-17", "Id": "Policy1544152951996", "Statement": [ { "Sid": "Stmt1544152948221", "Effect": "Allow", "Principal": { "AWS": "①" }, "Action": "s3:*", "Resource": "arn:aws:s3:::②" } ] } EOF )"
-
①にArn情報を入力し、②にバケット名を入力する
policy.jsonaws s3api put-bucket-policy --bucket furima41468 --policy "$(cat << EOF { "Version": "2012-10-17", "Id": "Policy1544152951996", "Statement": [ { "Sid": "Stmt1544152948221", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::263931220713:user/ユーザー名" }, "Action": "s3:*", "Resource": "arn:aws:s3:::furima41468" } ] } EOF )"
-
policy.json
の記述をCloudShellにペーストし、エンターキーを押す$ aws s3api put-bucket-policy --bucket furima41468 --policy "$(cat << EOF { "Version": "2012-10-17", "Id": "Policy1544152951996", "Statement": [ { "Sid": "Stmt1544152948221", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::263931220713:user/ユーザー名" }, "Action": "s3:*", "Resource": "arn:aws:s3:::furima41468" } ] } EOF )"
-
S3バケットのバケットポリシーを取得する
$ aws s3api get-bucket-policy --bucket furima41468
-
下記どちらかが表示されると、設定は全て完了している
{ "Version": "2012-10-17", "Id": "Policy1544152951996", "Statement": [ { "Sid": "Stmt1544152948221", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::263931220713:user/ユーザー名" }, "Action": "s3:*", "Resource": "Resource": "arn:aws:s3:::furima41468" } } ] }
{"Version": "2012-10-17", "Id": "Policy1544152951996", "Statement": [{"Sid": "Stmt1544152948221", "Effect": "Allow", "Principal": {"AWS": "arn:aws:iam::263931220713:user/ユーザー名"}, "Action": "s3:*", "Resource": "arn:aws:s3:::furima41468"}]}
- うまく行かない場合、下記を確認する
- 「①」の箇所に「ユーザーARN」を記述できているか
- 「②」の箇所に「作成したバケット名」を記述できているか
- ダブルクォーテーション("")を消してしまっていないか
- 全角スペースを入れてしまっていないか
- うまく行かない場合、下記を確認する
手順3(ローカル環境からS3に画像を保存する)
手順3-1(S3に保存する処理を記述)
-
ライブラリ
aws-sdk-s3
を導入する
Gem導入はこちらを参照 -
画像の保存先の設定を「:local」から「:amazon」に変更する
config/environments/development.rb# 中略 # 変更前 config.active_storage.service = :local # 中略
config/environments/development.rb# 中略 # 変更後 config.active_storage.service = :amazon # 中略
-
S3で使用するバケット名とリージョン名を記述する
※ymlファイルは不要なスペースやインデントによってエラーが発生する。下記をコピーして貼り付けることconfig/storage.ymltest: service: Disk root: <%= Rails.root.join("tmp/storage") %> local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 region: ap-northeast-1 bucket: furima41468 # 省略
※GitHubでソースコードを管理しているため、秘密情報である「Access key ID」「Secret access key」を直接storage.ymlに記載しない
悪意あるユーザーがGItHub内を探し回っていた場合、見つけた「Access key ID」「Secret access key」を使ってAWSのサービスを不正利用する危険性がある
手順3-2(環境変数を設定する)
- AWSからダウンロード済みの「ユーザー名_accessKeys.csv」を開く
- 環境変数を設定するファイルを編集するために、ターミナルで下記を実行する
% vim ~/.zshrc
- 「i」を入力し、ファイルを編集モードにする
※ターミナル左下に「INSERT」という文字が表示されれば文字が入力できる - 設定ファイルの最下部に、以下のコードを追加する
export AWS_ACCESS_KEY_ID="CSVファイルのAccess key IDの値を貼り付け" export AWS_SECRET_ACCESS_KEY="CSVファイルのSecret access keyの値を貼り付け"
- 既存のコードを消さないように注意
- ダブルクォーテーションを消さないように注意
- 「ACCESS_KEY_ID」と「SECRET_ACCESS_KEY」を逆に設定しないよう注意
- 「escキー」→「:wq」の順で入力し、環境変数の設定ファイルを保存する
- vimが終了し、通常のターミナルの状態に戻ることを確認する
- sourceコマンド入力し、先ほど設定した環境変数を使えるようにする
% source ~/.zshrc
- ターミナルを2つ以上開いている場合は、すべてのタブでsourceコマンドを実行する
- S3への認証情報を記述する
config/storage.yml
test: service: Disk root: <%= Rails.root.join("tmp/storage") %> local: service: Disk root: <%= Rails.root.join("storage") %> amazon: service: S3 region: ap-northeast-1 bucket: furima41468 access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> # 省略
手順3-3(git-secretsを設定して、セキュリティ対策する)
- ターミナルから、Homebrewを経由してgit-secretsを導入する
% cd ~/ #ホームディレクトリに移動 % brew install git-secrets
- 設定を適用したいアプリケーションのディレクトリに移動して、git-secretsを有効化する
% cd アプリケーション名 #開発中のアプリに移動 % git secrets --install
- AWS関連の秘密情報をcommitを防ぐ対象として設定する
% git secrets --register-aws --global
- 設定内容を確認する
% git secrets --list #=>以下、実行結果 secrets.providers git secrets --aws-provider secrets.patterns [A-Z0-9]{20} secrets.patterns ("|')?(AWS|aws|Aws)?_?(SECRET|secret|Secret)?_?(ACCESS|access|Access)?_?(KEY|key|Key)("|')?\s*(:|=>|=)\s*("|')?[A-Za-z0-9/\+=]{40}("|')? secrets.patterns ("|')?(AWS|aws|Aws)?_?(ACCOUNT|account|Account)_?(ID|id|Id)?("|')?\s*(:|=>|=)\s*("|')?[0-9]{4}\-?[0-9]{4}\-?[0-9]{4}("|')? secrets.allowed AKIAIOSFODNN7EXAMPLE secrets.allowed wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
- GitHub Desktopを使用してcommitなどソースコード管理を行なっている場合
- GitHub Desktopがアプリケーションのディレクトリに存在しているか確認する
- 下記コマンドで、GitHub Desktopにgit-secretsを適用する
% sudo cp /usr/local/bin/git-secrets /Applications/GitHub\ Desktop.app/Contents/Resources/app/git/bin/git-secrets
- sudoコマンドを実行した際にパスワードの入力が必要となる場合、PCにログインする際のパスワードを入力する
- 「No such file or directory」のエラーがでる場合は、GitHub Desktopのバージョンが古い可能性があるため、下記を実行する
% sudo cp /usr/local/bin/git-secrets /Applications/GitHub\ Desktop.app/Contents/Resources/git/bin/git-secrets
- 今後作成する全てのリポジトリに、git-secretsが適用されるようにするため、下記コマンドを実行する
% git secrets --install ~/.git-templates/git-secrets % git config --global init.templatedir '~/.git-templates/git-secrets'
手順3-4(ローカル環境で画像を保存できるか確認する)
- ローカル環境のアプリで画像を投稿する
- AWSのサイトにIAMユーザーでログインする
- AWSからダウンロード済みの「IAMユーザー名_credentials.csv」を開く
- 「Console login link」という欄のURLをクリックし、IAMユーザーのログイン画面にアクセスする
- csvファイルに記載されている「User name」「Password」を入力し、「サインイン」をクリックする
- MFAコードの入力画面に「MFAコード」を入力する
- AWSのサイト画面上部の検索フォームから「S3」を入力する
- 検索結果から「S3」を選択する
- 「バケット名」をクリックする
- 一度リロードして情報を最新の状態にする
- 「オブジェクト」という項目の中に投稿した画像があるか確認する
- 最終更新日時が、投稿した時間と一致していることを確認する
- オブジェクトをクリックして詳細ページを開く
- 「開く」をクリックし、表示された画像と投稿した画像が一致しているかを確認する
手順4(本番環境からS3に画像を保存する)
手順4-1(S3に保存する処理を記述)
-
画像の保存先の設定を「:local」から「:amazon」に変更する
config/environments/production.rb# 中略 # 変更前 config.active_storage.service = :local # 中略
config/environments/production.rb# 中略 # 変更後 config.active_storage.service = :amazon # 中略
手順4-2(環境変数を設定する)
- Renderにサインインし、ダッシュボードを開く
- 環境変数を設定したいアプリをクリックし、詳細ページを開く
- サイドバーにある「Environment」をクリックすると、環境変数の設定欄が表示される
-
Add Environment Variables
をクリックし、設定欄を追加したらAWS_ACCESS_KEY_ID
とAWS_SECRET_ACCESS_KEY
をそれぞれ入力する -
Save Changes
で保存する
手順4-3(本番環境で画像を保存できるか確認する)
- 本番環境のアプリにアクセスし、画像を投稿する
- AWSのサイトで画像を確認する
- AWSのサイトにIAMユーザーでログイン以降は、ローカル環境と同様の確認手順