AWS WAFで環境毎にCDN(CloudFront+S3)を立てた

この記事は Speeeアドベントカレンダー 18日目 の記事になります。 昨日(一昨日)は、@bino98さんによる、「Slackでヘタクソな日本語/英語の校正をしてくれる「Botlint」を作ってる」でした。

この記事ではAWS WAFを使って環境ごとにCDN環境を構築する方法についてまとめます。

やりたいこと

僕の担当サービスでは、Railsアプリのpublicディレクトリに配置したアセットをnginxで配信するという構成になっていました。

これを速度改善の一環として、S3に配置したアセットをCloudFront経由で配信するという構成に変更することになりました。 そのために行ったことをまとめておきます。

要件

ステージング環境もできるだけ本番環境に近づけるため、ProductionだけでなくStagingとStaging2(ステージング環境が2つある)にもCDNをたてることにしました。
StagingとStaging2には社外からアクセスしてほしくないので自社IPからのみ許可します。
また、S3バケットにURLで直接アクセスすることは制限します。

構成と設定の概要

上記要件を考慮した結果、以下のような構成・設定になりました。

f:id:manchose:20171219105014p:plain

構築の流れ

1. Amazon S3の設定

バケット作成時にアクセス権限を以下の設定にします。

このバケットにパブリック読み取りアクセス権限を付与しない (推奨)

こうすることで、S3のURLでオブジェクトにアクセスすることができなくなります。
全ての環境(Prod/Stg/Stg2)で同じバケットを利用してパスで出し分けることも考えたのですが、以下の理由でバケットをわけることにしました。

  • URLを変更することで別環境のアセットにアクセスできてしまう
  • terraformでバケットを作成する場合、すでにS3バケットが存在してしまうとコケる。

2. AWS WAFの設定

consoleでCreate Web ACLsをクリックするとセットアップウィザードが立ち上がるので、以下の項目を設定していきます。

IP Match Condition

許可/拒否に使うIPアドレスを登録します。
今回は3つIPアドレスを登録しました。

Rules

上記で登録したIPアドレスそれぞれに対して条件式を登録します。
それぞれに以下のRulesを作成しました。

When a request {does} {originate from an IP address in} {IP Match Condition}

これで「リクエストが上記で登録した3つの場合は」という条件式が登録されました。

Whitelist方式かBlacklist方式かを選択

上記で作成したRule(条件式)に当てはまった場合に「アクセスを許可するのか/アクセスを拒否するのか」を選択します。
また、Ruleに当てはまらなかった場合に「アクセス許可するのか/アクセス拒否するのか」も選択します。

3. Distributionの設定

Distributionも環境毎に作成しました。
理由は以下の2つです。

  • 本番にはWAFを設定しないので、少なくとも本番とそれ以外でDistributionをわける必要がある。
  • 1つのDistributionに複数のOriginを設定して送り先を振り分けるのは面倒そう。
Restrict Bucket Access

この設定をすることで、S3へのアクセスはオリジンアクセスアイデンティティからしかできなくなります。
Distributionにオリジンアクセスアイデンティティを作成するという項目が表示されるので、作成して紐付けることでバケットがCloudFrontからのみアクセスを受け付けるようになります。

SSL証明書バージニア北部で発行

ACMSSL証明書を発行して利用する場合、バージニア北部で発行する必要があります。
Asia/Tokyoで発行したものを使おうとしてハマってしまったのでご注意ください。

4. Route53にAliasレコードを追加

Route53にType AでAliasレコード(AWSが独自で作っているもの)を作成する必要があります。
今回でいえば環境ごとのアセットのドメイン(assets.my-domain.jpやassets.stg.my-domain.jp)へのリクエストをDistributionのxxxxxx.cloudfront.netに投げるように設定します。

5. Asset SyncでS3にアセットをアップロード

最後にCapistranoでのデプロイ時にAsset Syncで画像を指定バケットにアップロードするようにすれば準備は完了です。
manifest.jsonファイルはRailsアプリと一緒に配布する必要があるという点には注意する必要があります。
配布を忘れるとdigestの紐付けができなくなり404がでてしまいます。

まとめ

開発を手伝った韻ノート を使ったラップ講座をしようと思っていたのですが、全然まじめな内容になってしまいました。
次こそはラップ講座をしたいと思います。
明日(今日)は @nakana0226 さんで「スポンサー活動を本気でやってみた1年でした」です!お楽しみに!