静的コンテンツと動的な機能を持つサイトをCloudFrontを前面に配置する以下のような構成で構築した際のハマりどころををピックアップしてみる。

構成

  • CMSにMovable Type(MT)を採用(EC2上で起動)
  • サイトへのアクセスは全てSSL
    CloudFrontに独自証明書をアップロードし、適用。
  • CloudFrontで静的コンテンツと動的コンテンツを切り分け
    CloudFrontのマルチオリジン機能を使って静的なコンテンツはS3のオリジンへ、動的な機能はEC2のカスタムオリジンへ向くようオリジンとBehaviorsの設定を行う。
    Untitled

ハマりどころ

s3fsを使った公開バケットのマウントは避ける

MTは登録されているコンテンツを静的コンテンツとして出力する機能があり、CDPなんかでも静的コンテンツの出力先をs3fsでマウントしたS3のバケットにする事例もある。

しかしs3fsといってもネットワークを経由してS3にコンテンツをプットしているのでローカルのファイルシステムに比べて出力に時間がかかる。

コンテンツが増えると出力がいつまで経っても終わらないといったケースになるので、MTの出力先はローカルのファイルシステムとし、S3の同期はawscli等でs3 syncする。

MTの管理画面へのアクセスはCloudFrontを経由せず直接アクセスする

CloudFront経由でMTの管理画面にアクセスすることも可能だが、MTからコンテンツを静的化する際に時間がかかるとCloudFrontがタイムアウトしてしまう。

CloudFrontのタイムアウトは30秒でカスタマイズができないので、コンテンツが多いサイトでは管理用にCloudFrontを経由せずに直接EC2にアクセスしてサイトの構築ができるようにしておく。

S3のコンテンツアクセスする際のメタ文字の影響

WordPressのデフォルトパーマリンク設定をしてるとよく見かける

www.hoge.net/?p=2583

のように、コンテンツのファイル名に特殊文字(?とか)を含む場合、

www.hoge.net/%3F=2583

のようにURLエンコードする必要がある。

この変換自体はS3のバケットに以下のようなリダイレクトルールを設定することでリダイレクトをかけることが可能になる。

<RoutingRules>
    <RoutingRule>
        <Condition>
            <KeyPrefixEquals>?</KeyPrefixEquals>
        </Condition>
        <Redirect>
            <ReplaceKeyPrefixWith>%3F</ReplaceKeyPrefixWith>
        </Redirect>
    </RoutingRule>
</RoutingRules>

S3のコンテンツ取得時のindex.html補完問題

CloudFrontのDefault Root Objectの設定でCloudFrontで公開してるサイトにアクセスした際、www.hoge.netのようなURLでアクセスが来た場合、Default Root Objectで設定したファイルを取得してくれる。
しかし、これはあくまでRoot Objectであってwww.hoge.net/blog/のようなアクセスが来た場合は何もせず、403になる。この補完を実現したい場合はS3のStatic Site Hostingを有効にして、index documentを設定するしかない。

ただS3のStatic Site Hostingを有効にすると、CloudFront→S3間のアクセス制限でオリジンアクセスアイデンティティの設定はできなくなる。(してしまうとS3へのアクセスが403 Forbiddenになる。)

EC2で公開するコンテンツのアクセス制限

EC2で公開するコンテンツへのアクセスはCloudFrontからのみに制限したい。

現状ではセキュリティーグループでCloudFrontのようなAWSリソースからのアクセスを制限するような設定を行う事はできないので、セキュリティーグループでCloudFrontのIPを全て登録しそこからのアクセスのみ許可するよう設定することになる。AWSで利用されるIPは

https://ip-ranges.amazonaws.com/ip-ranges.json

で確認可能なので、

curl https://ip-ranges.amazonaws.com/ip-ranges.json | jq -r '.prefixes[] | if .service == "CLOUDFRONT" then .ip_prefix else empty end'

とか叩くと出てくる。

IPは随時追加されるので、

AWSリソースのip-ranges.jsonの差分チェックをAWS Lambda Function for Javaで作ってみた。

で書いたように差分チェックしておく。

User-Agentの転送

PCとスマホで出力するコンテンツをUser-Agentで判断して出し分けたい場合

  • S3のコンテンツ
    S3のバケットのリダイレクトルールではUser-Agentを判定したルールは記載できないので、JavaScriptのnavigator.userAgentで頑張る。
  • EC2のコンテンツ
    EC2のオリジンへアクセスするCloudFrontのBehaviorの設定でFoward Headersを「All」にするか「Whitelist」にしてUser-Agentを登録する。
    User-Agentがオリジンにフォワードされるとその多様さからCloudFrontがコンテンツをキャッシュすることはほぼ無くなるけど、動的コンテンツなので気にしない。

CloudFront→EC2間のSSL通信

CloudFrontとEC2間の通信をSSL化する場合、以下の条件によって必要な証明書の数が異なる。
仮にCloudFrontでwww.hoge.netでサイト公開し、EC2へのec2.hoge.netで名前解決してアクセスできる状態とすると。

  • HostヘッダがEC2にフォワードされる場合
    CloudFrontのBehaviorの設定でForward Headersの設定を「All」に設定するか「Whitelist」に設定してHostヘッダはフォワードするように設定した場合、EC2のWebサーバに組み込むSSL証明書はCloudFrontに登録してある証明書と同じCommon Nameがwww.hoge.netの証明書になる。
  • HostヘッダがEC2にフォワードされない場合
    CloudFrontのBehaviorの設定でForward Headersの設定を「None」に設定するかWhitelist」に設定してHostヘッダはフォワードしないように設定した場合、EC2のWebサーバに組み込むSSL証明書はCommon Nameがec2.hoge.netの証明書になる。

これはCloudFrontがHostヘッダに含まれる値で証明書のCommon Nameを確認しているため。

証明書を節約する場合、前者の方法が向いているけど、MTの管理画面にCloudFrontを経由せず直接SSLでアクセスしたい場合はいずれにせよ証明書は2つ用意することになる。

希望

日々機能追加が進むAWSのについて↓な機能が実現されるとこういったサイト公開の際に助かるなーって思ってます。

  • CloudFrontのタイムアウト期間を設定できるようになるとうれしい。
  • S3のStatic Site Hostingを有効にしなくても、CloudFrontのDefault Root Objectがパス以下のコンテンツに対しても適用できるようになるとうれしい。
  • S3のバケットのリダイレクトルールにUser-Agentによるルール設定ができるようになるとうれしい。
  • セキュリティーグループの設定で任意のAWSリソースからのアクセス制御が設定できるようになるとうれしい。