Timee Product Team Blog

タイミー開発者ブログ

S3バケットの構成標準化 - 分類とガードレールによる運用改善

はじめに

こんにちは、タイミーでエンジニアをしている古屋(id:kimikimi714 / @kimikimi714)です。
こちらはTimee Product Advent Calendar 2025トラック3の7日目の記事です。
すでに入社して1年半ほど経ちましたが、入社して割とすぐに対応した S3 バケットの構成標準化について公開します。

標準化の目的

当時、ある S3 バケットの調査をおこなおうとした際に以下のような問題が見つかりました。

  • Terraform で IaC 化される前に作成されたバケットなどで、S3 へのアクセスログが出力されてないケースがある。
  • パブリックアクセスの必要のないバケットに対して、パブリックアクセスブロックの設定が入っていないものがある。
  • 仮に IaC 化されていたとしても、書いた人によって入れられた設定がまちまちで一貫性がない。

IaC 管理されていないなら import すればいいじゃない、ログが取れてないなら取ればいいじゃないという話ではなく、そもそもどういう状態にあれば既存サービスおよびこれから立ち上げていく新規サービスでも S3 を安全にしっかり使える状態になるのかがわかっていないことが根本の問題でした。

このため、S3 バケットのベストプラクティスをまずは押さえて、その上で私たちがどのようにそのプラクティスを取り入れていくべきかをまとめて標準化とする方針で進めました。

セキュリティベストプラクティスの確認

まずは AWS 公式が提供しているベストプラクティスの確認からおこないました。

docs.aws.amazon.com

これらは AWS Security Hub CSPM の基本的なセキュリティのベストプラクティス標準が有効であれば自動的にコンプライアンスチェックがされるものも含まれていますが、たとえば以下のようなものは含まれないために自分たちで必要なチェック体制を組む必要があります。

例として挙げたチェック項目はすべてのバケットに対して有効化するべきか?というとそうではありません。

たとえば、バージョニングは過去バージョンで保存されているオブジェクトサイズの分だけコストがかかります。また過去バージョンを抱えているオブジェクトの削除マーカーが増えすぎたり、1オブジェクトあたりの過去バージョン数が多いとパフォーマンス上の問題があることもバージョニングのトラブルシューティングに書かれています。バージョニングは過去バージョンによるバックアップで障害復旧することが主な目的となるでしょうが、バックアップをバージョニングでおこなうべきなのか、 GitHub Actions 等からのアップロードで対応しているなら問題があれば GitHub から切り戻せる余地はないのかなど要件次第で要不要が変わるものです。同様にオブジェクトロックも要不要は要件次第です。

また、オブジェクトロックはバケットの作成時でないと設定できない制約もあるので、作成時点で要件をある程度固めてからでないと適応できない設定項目だったりします。

こういったことからベストプラクティスをベースとしつつも、すべてのバケットに適応するべき項目とバケットの大まかな分類をして分類ごとに必要なプラクティスをよりわけました。

バケットの分類

新たに出てくるバケットの要件が何になるかはその時になってみないとわからないです。なので、まずは既存バケットの利用方法について調査するところからはじめました。そうすると大雑把に以下のような8つの分類となることがわかりました。

  • ユーザーのプロフィール画像など
  • コーポレートサイトのクリエイティブなど
  • S3 サーバーアクセスログ
  • CloudTrail ログなどの監査ログ
  • アプリケーションログのアーカイブ
  • アプリケーションで利用する設定ファイル
  • アプリケーションで参照する一時ファイル
  • 要配慮個人情報や特定個人情報

それぞれの分類について簡単に解説します。最終的な標準設定は別途記載します。

ユーザーのプロフィール画像など

ユーザーによるアップロードで作成されるオブジェクトが主な内容です。これらはユーザーが能動的に画像をアップロードしなければならないため、仮に誤って削除・更新してしまうと、復旧はバックアップからかユーザーからの再アップロードとなってしまいます。誤操作による再アップロードを促す流れは、ものによってはユーザー離脱の引き金になり得るので、こういうケースはバージョニングしておいた方が安心です。

コーポレートサイトのクリエイティブなど

コーポレートサイトや何らかのランディングページの静的リソースが主な内容です。これらは GitHub のリポジトリで管理し、マージしたら GitHub Actions 等で自動的にアップロードされることが多いです。画像保存のための別のツールをデザイナーが使っているケースもありますが、大半は GitHub を通じてアップロードされるためユーザーに見えるリソースの大元は S3 以外にあるようなケースです。

もしリソースが何らかの理由で削除・更新された場合に過去バージョンから復元するのか、 GitHub からアップロードをするのかは復旧までのリードタイム要件次第となります。ビルドが挟まるなど、デプロイ時間が復旧時間に強く影響を与えるケースではバージョニングから復旧できた方が早いですし、リバートしてデプロイの方が一瞬でもレビューが挟まって安全に復旧できるという考え方もできます。また削除・更新されたオブジェクトの数が多すぎれば、バージョニングから1つずつ戻すことが現実的ではないこともあり得ます。

S3 サーバーアクセスログ

S3 へのアクセスがおこなわれた際に S3 バケットから発行されるアクセスログの保存場所です。このアクセスログは実際のアクセスから数時間遅れて発行される特徴があるため、 CloudTrail のようなリアルタイム性はかなり低いです。ですが、 CloudTrail データイベントを有効化しないと取れないものがより料金が安く取れたり、CloudTrail ログには含まれない情報が含まれていたりするため、有効化しておいた方が監査性が上がります。

比較表は以下のリンクをご確認ください。

docs.aws.amazon.com

CloudTrail ログなどの監査ログ

CloudTrail などインシデント時に誰がどこからアクセスしたか追跡する用途で使われるような監査ログを保存する場合です。改竄防止が非常に大事になります。

アプリケーションログのアーカイブ

すでに弊社のいくつかのテックブログで書いてる通り、 普段は Datadog を利用していますが、ログの長期間保存は非常にコストがかかるため S3 にアプリケーションログのアーカイブを取って Datadog 側の保持期間は短くしてあります。

平時に参照することはほとんどありませんが、過去の事象に関する調査をしたい場合に活用されるので、数年保存することを想定しているものになります。

アプリケーションログの他、 ALB や CloudFront などのアクセスログもここに含むことにしています。

アプリケーションで利用する設定ファイル

アプリケーションが設定ファイルとして読み込むファイルの保存先です。たとえば ECS で参照する環境変数が列挙されたオブジェクトFluent Bit の設定ファイルなどが主な内容です。

アプリケーションで参照する一時ファイル

たとえば、一度 DB レコードから集計したデータを一時的にオブジェクトとして S3 バケットにおき、別のバッチで参照するようなケースで使われる一時ファイルです。バッチが実行終了したら使わなくなるようなファイルが主で、利用後直ちに削除することが期待されるようなオブジェクトを保管するバケットです。

要配慮個人情報や特定個人情報

法的に個人情報保護の中でも要件が強いものを保管するバケットです。S3 バケットのバケットポリシーでアクセスコントロールをおこなうことが大半ですが、このバケットについては特にコンプライアンス遵守のために KMS による暗号化とキーポリシーによるアクセスコントロールを追加でおこなうようにすることを検討した方が良いです。

分類後のバケット設定のサマリー

分類後、それぞれに必要と考えられる設定をそれぞれ書き出しました。以下の表が簡単なサマリーです。本当はここに書いてる項目以外にもありますが、全部書くには多すぎるので一部だけ掲載しています。これが唯一の解ではありませんし、S3 バケットに対して設定できることはここに書いていることだけではないですが、こういったことをまとめることで「どのバケットに何の設定がされている必要があるのか」という標準化に近づけることができます。

バケットの分類 暗号化手法 要オブジェクトロック 要バージョニング 要レプリケーション
ユーザーのプロフィール画像など S3-managed 不要 必要 必要
コーポレートサイトのクリエイティブなど S3-managed 不要 不要 不要
S3 サーバーアクセスログ S3-managed レプリケーション先で必要*1 必要 必要
CloudTrail ログなど監査ログ S3-managed 必要 必要 不要
アプリケーションログのアーカイブ S3-managed 不要 不要 不要
アプリケーションで利用する設定ファイル S3-managed 不要 必要 必要
アプリケーションで参照する一時ファイル S3-managed 不要 不要 不要
要配慮個人情報や特定個人情報 KMS 不要 不要 不要

この設定例を元に S3 バケットのサンプルコードも作成しました。このコードには S3 サーバーアクセスログのバケットの指定方法なども含めて、課題として挙げていたログの欠如を解消し、ログを取ることが当たり前とされるような構成を目指しました。また、パブリックアクセスブロックも CloudFront を前段に挟めばバケットそのものを公開する必要はないことから基本的には全バケットでプライベート化を推奨*2し、できれば AWS アカウント単位でパブリックアクセスブロックを有効化しておくこととしました。このとき、チームメンバーから module 化することで、こういった設定そのものをカプセル化した方が良いのではないか?という提案がありましたが、作成した当時この分類で本当に問題ないのかがわからなかったため、 module 化は時期尚早と判断し、標準化の第一段階としてはサンプルコードを書くに留めました。

ガードレールの設計と実装

セキュリティベストプラクティスの確認 」の節にも記載した通り、AWS Security Hub CSPM の基本的なセキュリティのベストプラクティス標準が有効であれば自動的にコンプライアンスチェックがされるものも含まれているため、含んでいないもののうち、バケットの分類上必要なチェック項目を洗い出しました。これらは AWS Configマネージドルールが存在しています。 AWS Config は AWS リソースの構成を継続的に監視・評価するサービスで、 SecurityHub とも統合が可能なものとなっています。先に洗い出した項目と対応関係は以下のようになります。

意味 ルール名
S3 バージョニングが有効か s3-bucket-versioning-enabled
S3 オブジェクトロックがかかっているか s3-bucket-default-lock-enabled
サーバーサイド暗号化が有効か s3-bucket-server-side-encryption-enabled
KMS で暗号化されているか s3-default-encryption-kms
レプリケーションが有効か s3-bucket-replication-enabled

AWS Config では特定のタグがついているリソースに対して、特定のルールをチェックする機構があります。これらのルールをひとまとめにした適合パックを各 AWS アカウントに反映して、リソースには適切にタグをつけることで標準に沿った構成かチェックできるようにしました。タグの設定例は以下のようにしました。

tags = {
  "BucketType"                                          = "user-uploads"
  "ConfigRule-s3-bucket-replication-enabled"            = "enabled"
  "ConfigRule-s3-bucket-server-side-encryption-enabled" = "enabled"
  "ConfigRule-s3-bucket-versioning-enabled"             = "enabled"
}

これで ConfigRule-s3-bucket-replication-enabled がついているリソースでは s3-bucket-replication-enabled がチェックされるようになります。実際にはタグのまとまりを module 化しており、コード上の指定としては以下のように書くだけで中のタグが展開されて、リソースの構成チェックが走る仕組みです。

tags = module.aws_config_s3.tag_map_for_user_uploads

module の修正をおこなうことで中央集権的にチェック項目を管理でき、かつ仮にルールの一部を緩めたい場合は以下のように書くことで柔軟性も持たせました。

tags = merge(
  module.aws_config_s3.tag_map_for_user_uploads,
  {
    "ConfigRule-s3-bucket-replication-enabled" = "disabled"
  }
)

運用開始後に見えてきた良かったこととギャップ

まず、この標準化の良かったところは、その時点でできる S3 の設定について網羅的に知れたこととそれをちゃんと要件に組み込むサンプルまで出せたことです。バージョニングができることは以前から知っていたものの、バージョニングによるパフォーマンス問題や過去バージョン分だけコストが上がるなどの制約は標準化前の調査をしてはじめて知ったことでした。チームメンバーも知らない制約だったりしたので、それをわかった上でどういう要件で役に立つのかなどまとめられたことは良かったです。

また、バケット作成時点でどういう設定を入れればいいか迷っていた時間もこの分類とサンプルコードのおかげで減りました。ここは今になって思えば設計から実装時間の計測をしておけば、どのくらい削減できたか少しはわかったかもしれないなと思っていますが、一から S3 バケットで何ができるか調べるよりは分類から入れるべき設定をサクッと出せるのは個人的な体験としても良かったです。

一方でギャップもやはりありました。

この標準化の目的は要件ごとに S3 バケットの適切な設定を決めておくことでスムーズにコンプライアンスを遵守した S3 バケットを作成・更新できるようにすることでしたが、ここから発展させてあまり S3 に詳しくない人であっても、この分類に沿って自分たちの要件にあった S3 バケットを作れるようになることを目標においていました。そこで第一段階としては私が標準化したバケット設定を使って新規バケットを作り、期待値通りのチェックもおこなわれるかを確認し、第二段階として開発者からの依頼のあったバケットを試しに作ってもらえないか相談して実践してみました。

第一段階は私が作った本人でもあることから期待通りになることはすんなり確認できましたが、第二段階ではやはり S3 に関する知識が十分にない状態ではいくらサンプルコードがあっても書くのは難しいと改めてわかりました。この件は普段 Terraform を使わないような方にお願いをしたので、元の知識にギャップがある状態でお願いするのがそもそも無茶な話だったとは思います。ただ、それ以降 Terraform に詳しいチームメンバーにもいくつかのバケットを作ってもらう機会がありました。その際、私のコードレビュー時点で抜け漏れや分類の認識違いが見つかったので、 module 化をすれば設定の抜け漏れはなくなるかもしれないですが、適切にバケットの種類を選べるかにはまだハードルがあると感じました。

ギャップの中には私の予想を外れた良いギャップもありました。それはバケットの分類が最初に決めた去年の7月ごろから今の時点まで8つの分類を修正する必要がなかったことです。私自身はこの分類にはじめから自信があったわけではないですし、分類が変わらなくても中の設定の修正が必要な可能性を考慮したために module 化を見送っていたのですが、実際には S3 に関する各種アップデートを含めても今変更を加えないといけない理由がないのが実情です。こんなに変わらないなら module 化しておけばよかったなと思うほどでした。

今後の展開

今年11月20日から21日で開催されたアーキテクチャConference2025にて、弊社橋本による発表『AI x Platform Engineeringでスケーラブルな組織を作るには』でも公開された内容に関連して、この施策は現在 Design Addendum(設計判断を文書化したドキュメント、補遺) とし、この内容を元に AI が自走できるように検討を進めているところです。

すでに Design Addendum として S3 の分類やその思想を書き出したところなので、これを AI が読み自分で実装できるか?を試そうとしています。実装できると思っていますが、目標の第二段階にあった「あまり S3 に詳しくない人であっても、この分類に沿って自分たちの要件にあった S3 バケットを作れるようになる」に足る状態になるかはしっかり検証したいと思っています。

まとめ

以上が S3 バケットの構成標準化のためにバケットの分類をおこない、ガードレールを作成したアプローチです。実際の成果として設計時間の削減や、8つのバケットの分類でほぼ実際の要件をカバーできることが得られました。

このドキュメントを参考にされる場合は、まずは既存に存在するバケットをざっくり分類してみることが良いと思っています。中に含まれるオブジェクトの種類が無茶苦茶になっているバケットがある場合も予想できますが、たとえば AWS の ALB ログなどは出力形式が決まっていたりするので、そういうわかりやすいものから分類していくと結果が収束していくように思います。また「うちではこうやって標準化進めてるんだよー」みたいな話が聞けるとうれしいです。

この施策を実施した時点では今ほど AI 活用が進んでおらず、1年足らずでこんなに使えるツールとなるとは思っていませんでした。内心本当に module 化しか手段はないのか?と思いながら分類までは作っていたので、AI に流用できそうな発展性が生まれてきて個人的に良い流れだなと思っています。知識が少ない人でも越境しやすい素地がここから生まれるように、これからもやっていきです!

We are hiring!

この記事にあるような「こんな時どうしたらいいんだろう?」と思う、舗装されてない箇所はまだまだあります。一緒にみんなが安心して走れる Paved road 作りやっていきたい方、ご興味があればぜひお話ししましょう!

プロダクト採用サイトTOP

カジュアル面談申込はこちら

*1:S3 サーバーアクセスログはアクセスログが格納されるバケットを直接オブジェクトロックをかけるとログが送られないことが検証でわかりました。このため、S3 サーバーアクセスログを監査目的で保存したい場合、手間ですがオブジェクトロックがかかった別のバケットを用意してレプリケーション先が改竄されないようにする方法があります。ログが格納される最初のバケットこそロックをかけたいですが、現状の仕様ではできません。

*2:静的ウェブサイトホスティングを利用するケースでは S3 バケットがパブリックである必要があります。こういうケースではパブリックアクセスブロックができないことに注意が必要です。また、AWS アカウント単位でパブリックアクセスブロックを有効にすると S3 バケット個別のパブリックアクセスに関する設定が無視される仕様があるので、静的ウェブサイトホスティングを利用する S3 バケットがあるアカウントではアカウント単位のパブリックアクセスブロックができないことにも注意が必要です。